43

In a Cocoa Touch project, I need a specific class to have not only a single delegate object, but many of them.

It looks like I should create an NSArray for these delegates; the problem is that NSArray would have all these delegates retained, which it shouldn't (by convention objects should not retain their delegates).

Should I write my own array class to prevent retaining or are there simpler methods? Thank you!

wh1t3cat1k
  • 3,051
  • 6
  • 29
  • 36
  • 3
    You almost certainly don't really want to do this. Usually in a situation like this your code should broadcast `NSNotification`'s which multiple objects (the delegates in your question) subscribe to. – Mike Abdullah Jan 14 '11 at 16:40
  • 4
    Mike, I'm afraid I want - for control and security reasons. I want to know what kind of things are attached to my delegator. – wh1t3cat1k Jan 16 '11 at 20:23
  • 2
    I'd suggest to not-fight-the-framework and use NSPointerArray with the NSPointerFunctionsWeakMemory NSPointerFunctionOption – leviathan Jun 06 '13 at 08:18
  • perfectly valid requirement – dave Jul 16 '16 at 18:13

10 Answers10

48

I found this bit of code awhile ago (can't remember who to attribute it to).

It's quite ingenius, using a Category to allow the creation of a mutable array that does no retain/release by backing it with a CFArray with proper callbacks.

@implementation NSMutableArray (WeakReferences)
    + (id)mutableArrayUsingWeakReferences {
    return [self mutableArrayUsingWeakReferencesWithCapacity:0];
    }

    + (id)mutableArrayUsingWeakReferencesWithCapacity:(NSUInteger)capacity {
    CFArrayCallBacks callbacks = {0, NULL, NULL, CFCopyDescription, CFEqual};
    // We create a weak reference array
    return (id)(CFArrayCreateMutable(0, capacity, &callbacks));
    }
@end

EDIT Found the original article: http://ofcodeandmen.poltras.com

MarkPowell
  • 16,294
  • 7
  • 57
  • 76
  • 3
    Just like with the suggestion to use interim NSValue objects for storing the weak refs, I believe this solution will not store true weak refs that get set to nil if the target object gets deallocated. This is just a suspicion of mine, though - correct me if I'm wrong. The safest way might still be to define your own helper object providing a weak property, and storing that helper object in the collection (array, dictionary, set), as suggested in the comment on the NSArray use. – Thomas Tempelmann Nov 12 '12 at 21:02
  • In fact, I added an answer about this to a similar question, see here: http://stackoverflow.com/a/13351665/43615 – Thomas Tempelmann Nov 12 '12 at 21:19
  • Just tested in iOS 10. I don't believe it's working anymore, the element that's being added it's a string like: "iphoneos10.1". Very strange though. – Paul Razvan Berg Jan 16 '17 at 19:16
  • Alternatively, just use `CFArrayCreateMutable(…)` in your client code, using it as you would an `NSMutableArray` _(e.g. `[(NSMutableArray *)myCFArray addObject:aDelegate]` or just `[(id)myCFArray addObject:aDelegate]`)_.  This works because `NSMutableArray` is “toll-free bridged” with `CFMutableArrayRef`.  More info in the [Apple Developer article on Toll-Free Bridging](https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/Toll-FreeBridgin/Toll-FreeBridgin.html#//apple_ref/doc/uid/TP40010810-CH2). – Slipp D. Thompson Jul 26 '18 at 12:09
27

I am presenting an important limitation of one of the earlier answers, along with an explanation and an improvement.

Johnmph suggested using [NSValue valueWithNonretainedObject:].

Note that when you do this, your reference acts not like __weak, but rather like __unsafe_unretained while inside the NSValue object. More specifically, when you try to get your reference back (using [myNSValue nonretainedObjectValue]), your application will crash with an EXC_BAD_ACCESS signal if the object has been deallocated before that time!

In other words, the weak reference is not automatically set to nil while inside the NSValue object. This took me a bunch of hours to figure out. I have worked around this by creating a simple class with only a weak ref property.

More beautifully, by using NSProxy, we can treat the wrapper object entirely as if it is the contained object itself!

// WeakRef.h
@interface WeakRef : NSProxy

@property (weak) id ref;
- (id)initWithObject:(id)object;

@end


// WeakRef.m
@implementation WeakRef

- (id)initWithObject:(id)object
{
    self.ref = object;
    return self;
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    invocation.target = self.ref;
    [invocation invoke];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    return [self.ref methodSignatureForSelector:sel];
}

@end
Timo
  • 6,027
  • 4
  • 38
  • 52
  • Holy crap...AWESOME! Thanks! NSProxy looks fun to play with. – walkingbrad Jul 28 '14 at 02:18
  • 1
    Note: For best performance with `NSProxy`s, you should implement `- (id)forwardingTargetForSelector:(SEL)aSelector` in addition to `- forwardInvocation:` _(I know, I know, `-forwardingTargetForSelector:` is neither in the `NSProxy` nor `NSObject`-protocol declarations, but the Obj-C runtime uses it anyway for `NSProxy`-subclasses)_. Also, the runtime will check `- (BOOL)respondsToSelector:(SEL)aSelector` _(and sometimes `- (BOOL)conformsToProtocol:(Protocol *)aProtocol`)_ first, so you'll need to implement those too for it to use `- forwardingTargetForSelector`. But the speed diff is worth it! – Slipp D. Thompson Jul 26 '18 at 12:24
17

Check documentation of NSValue valueWithNonretainedObject method :

This method is useful for preventing an object from being retained when it’s added to a collection object (such as an instance of NSArray or NSDictionary).

Johnmph
  • 3,337
  • 21
  • 31
  • 26
    Note that when you do this, your reference acts NOT like __weak, but rather like __unsafe_unretained while inside the NSValue object. More specifically, when you try to get your reference back (using [myNSValue nonretainedObjectValue]), your application will crash with an EXC_BAD_ACCESS signal if the object has been deallocated before that time. In other words, the weak reference is not automatically set to nil while inside the NSValue object. This took me a bunch of hours to figure out. I have worked around this by creating a simple class with only a weak ref property. Add to array, voila! – Timo Jul 12 '12 at 10:17
  • 2
    @Timo your comment needs more visibility. post it as an answer – codrut Apr 29 '13 at 12:03
  • I've posted my comment as an answer and added a sexy alternative that utilizes NSProxy. – Timo Aug 06 '13 at 12:41
13

I'd suggest to not-fight-the-framework and use NSPointerArray with the NSPointerFunctionsWeakMemory NSPointerFunctionOption like this:

NSPointerArray *weakReferencingArray = [NSPointerArray pointerArrayWithOptions:NSPointerFunctionsWeakMemory];

// NSPointerFunctionsWeakMemory - Uses weak read and write barriers 
// appropriate for ARC or GC. Using NSPointerFunctionsWeakMemory 
// object references will turn to NULL on last release.

Served me well in scenarios, where I had to design a delegates array, which auto-NULL's references.

leviathan
  • 10,700
  • 5
  • 39
  • 39
12

You do not want to do this! Cocoa Touch have several concepts for sending events, you should use the proper concept for each case.

  1. Target-action: For UI controls, such as button presses. One sender, zero or more receivers.
  2. Delegates: For one sender and one receiver only.
  3. Notification: For one sender, and zero or more receivers.
  4. KVO: More fine grained that notifications.

What you should do is to look into how to use NSNotificationCenter class. This is the proper way to send a notification that have more than one receiver.

PeyloW
  • 36,308
  • 12
  • 75
  • 98
  • Although not the answer the original author was looking for, I needed someone to lay it out, and have some exclamations marks to make sure I listen... :) – Oded Ben Dov Jun 01 '11 at 15:01
  • 10
    However, there *are* situations where a collection of nonretained objects is useful. – matt Nov 23 '11 at 23:57
  • 1
    @matt - It is, for the few and rare cases you have `NSHashMap` on Mac OS X, or can drop down to `CFDictionary` on both iOS and Mac OS X. – PeyloW Dec 07 '11 at 19:51
4

This one from NIMBUS would be more simple:

NSMutableArray* NICreateNonRetainingMutableArray(void) {
  return (NSMutableArray *)CFArrayCreateMutable(nil, 0, nil);
}

NSMutableDictionary* NICreateNonRetainingMutableDictionary(void) {
  return (NSMutableDictionary *)CFDictionaryCreateMutable(nil, 0, nil, nil);
}

NSMutableSet* NICreateNonRetainingMutableSet(void) {
  return (NSMutableSet *)CFSetCreateMutable(nil, 0, nil);
}
Bartosz Ciechanowski
  • 9,995
  • 4
  • 41
  • 58
flypig
  • 1,230
  • 1
  • 18
  • 23
3

Keyword: NSHashTable, search in documentations.

Klein Mioke
  • 1,026
  • 9
  • 21
  • Holy crap, I don't know how I didn't know (forgot?) about this.  More needs to be said about this godsend of a class.  From Apple's docs: _The hash table is modeled after NSSet with the following differences: 1. **It can hold weak references to its members.** 2. Its members may be copied on input or may use pointer identity for equality and hashing. 3. It can contain arbitrary pointers (its members are not constrained to being objects)._  **TL;DR: This is the “right” answer— one that uses a built-in NSFoundation class specifically designed by NeXT/Apple to solve your issue.** – Slipp D. Thompson Jul 26 '18 at 12:15
2

I found some pieces of code from Three20 project about this topic, i hope this helps...

NSMutableArray* TTCreateNonRetainingArray() {
  CFArrayCallBacks callbacks = kCFTypeArrayCallBacks;
  callbacks.retain = TTRetainNoOp;
  callbacks.release = TTReleaseNoOp;
  return (NSMutableArray*)CFArrayCreateMutable(nil, 0, &callbacks);
}


NSMutableDictionary* TTCreateNonRetainingDictionary() {
  CFDictionaryKeyCallBacks keyCallbacks = kCFTypeDictionaryKeyCallBacks;
  CFDictionaryValueCallBacks callbacks = kCFTypeDictionaryValueCallBacks;
  callbacks.retain = TTRetainNoOp;
  callbacks.release = TTReleaseNoOp;
  return (NSMutableDictionary*)CFDictionaryCreateMutable(nil, 0, &keyCallbacks, &callbacks);
}
flypig
  • 1,230
  • 1
  • 18
  • 23
0

I found an open source library named XMPPFramewrok

There is a multicast delegate solution in the project

https://github.com/robbiehanson/XMPPFramework/wiki/MulticastDelegate

Hailong
  • 1,064
  • 10
  • 9
0

What about storing in the array or dictionary

__weak typeof(pointer) weakPointer = pointer;
Nicolas Manzini
  • 7,809
  • 6
  • 58
  • 77