22

I would like to store a zeroing weak reference to an object in a NSDictionary. This is for a reference to a parent NSDictionary, so I can crawl back up a large structure without searching.

I can not use __weak here; even if my local reference is weak, the NSDictionary will store a strong reference to the object that was weakly referenced. And, of course, NSDictionary can't have nil objects.

I'm on iOS, not Mac, so NSHashTable isn't available. And I only want one object to be weak; the rest should still be strong.

(I'm going to post my answer, so I have something to mark as accepted if there's no better answer. But I'm hoping someone has a better answer.)

Steven Fisher
  • 44,462
  • 20
  • 138
  • 192
  • I created [this](https://gist.github.com/firelizzard18/6326536) to be a dictionary that stores objects as effectively zeroing weak references. It could be modified (and cleaned up) to serve your purposes. – Ethan Reesor Aug 24 '13 at 07:03

3 Answers3

39

In iOS 6+ you can use NSMapTable and choose if you want objects and/or keys to be weak or strong.

João Nunes
  • 3,751
  • 31
  • 32
  • 2
    Clearly the best answer for today and forward. Thanks. – Steven Fisher Feb 15 '13 at 02:59
  • 5
    NSMapTable might not behave as expected: http://cocoamine.net/blog/2013/12/13/nsmaptable-and-zeroing-weak-references/ – nehz Jun 06 '15 at 18:00
  • 1
    NSMapTable will do the job. But still curious about how `valueWithNonretainedObject` works? It holds a `__weak` reference or `__unsafe_unretained ` one. @Timo say [it works like `__unsafe_unretained `](http://stackoverflow.com/a/18080450/531966). Is that correct? – Nickolas Apr 07 '16 at 09:47
  • That is easy to test. Make an autorelease pool. Set the value of the key to your object. "destroy" your object. Then after the autorelease pool check the value of the key – João Nunes Apr 09 '16 at 09:24
  • @Nickolas nsmaptable doesn't support the options you ask! – João Nunes Apr 09 '16 at 09:32
15

I've settled on defining a "container" class with a single member, like this:

@interface Parent : NSObject
@property (nonatomic, weak) id parent;
@end

@implementation Parent

@synthesize parent = _parent;

- (id)initWithParent: (id)parent;
{
    if (( self = [super init] )) {
        _parent = parent;
    }
    return self;
}
@end

And using it:

id parentRef = [[Parent alloc] initWithParent: parent];
[newElement setObject: parentRef forKey: ParentKey];

I think this will work for me, but it seems crazy that there's no better way built in to Foundation.

Steven Fisher
  • 44,462
  • 20
  • 138
  • 192
  • Sounds like the only solution to me - that's how I would have done it as well. Or an `NSValue` but that wouldn't zero out. – mattjgalloway Jan 06 '12 at 18:56
  • 1
    I'm having the same problem and I also thought about a container, I still didn't tried but I'm trying to make make something close to what you do with block `id __weak weakObject=strongObject; [optionDict setValue:[NSValue valueWithNonretainedObject:tmpObject] forKey:key]; ` still need to figure out if it works. – Andrea Mar 06 '12 at 14:02
  • 1
    Old comment, but I'll add this one for new visitors. What Andrea says won't work, the dictionary would retain the object anyway. – Emanuel Dec 10 '14 at 18:39
7

There is a built-in way; just use:

[array addObject:[NSValue valueWithNonretainedObject:object]];

Then to access the object later, use:

id object = [[array objectAtIndex:i] nonretainedObjectValue];

If the value has been released since you added it then "object" will be an invalid pointer under iOS 4.x, and (I'm assuming) will be nil under 5.x with ARC enabled.

EDIT: my assumption was wrong - it's not weak under ARC (see discussion below).

Nick Lockwood
  • 40,865
  • 11
  • 112
  • 103
  • The dangling pointer should never be accessed, though, as your object is close-to-death at that moment. So accessing the parent here should be a bug anyway, which in this case isn't hidden by niling out. – Eiko Nov 22 '12 at 17:09
  • 2
    @Eiko, you're wrong. It's a common case for observer pattern. – Aleks N. Nov 29 '12 at 14:19