Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Collections of zeroing weak references under ARC

How can I get an array of zeroing weak references under ARC? I don't want the array to retain the objects. And I'd like the array elements either to remove themselves when they're deallocated, or set those entries to nil.

Similarly, how can I do that with a dictionary? I don't want the dictionary to retain the values. And again, I'd like the dictionary elements either to remove themselves when the values are deallocated, or set the values to nil. (I need to retain the keys, which are the unique identifiers, at least until the corresponding values are deallocated.)

These two questions cover similar ground:

  • NSArray of weak references to objects under ARC
  • Having a list of unretained id objects?

But neither is asking for zeroing references.

Per the documentation, neither NSPointerArray nor NSHashMap support weak references under ARC. NSValue's nonretainedObjectValue will not work either, as it is non-zeroing.

The only solution I see is to create my own NSValue-like wrapper class with a (weak) property, as this answer mentions, near the end. Is there a better way I'm not seeing?

I'm developing for OS X 10.7 and iOS 6.0.

like image 224
paulmelnikow Avatar asked Jan 08 '13 06:01

paulmelnikow


People also ask

What is the purpose of a weak reference?

A weak reference permits the garbage collector to collect the object while still allowing the application to access the object. A weak reference is valid only during the indeterminate amount of time until the object is collected when no strong references exist.

What is a weak reference in programming?

In computer programming, a weak reference is a reference that does not protect the referenced object from collection by a garbage collector, unlike a strong reference.

What is the purpose of a weak reference in C sharp?

A weak reference allows the garbage collector to collect an object while still allowing an application to access the object. If you need the object, you can still obtain a strong reference to it and prevent it from being collected.

What are strong and weak references?

A weak reference is just a pointer to an object that doesn't protect the object from being deallocated by ARC. While strong references increase the retain count of an object by 1, weak references do not. In addition, weak references zero out the pointer to your object when it successfully deallocates.


2 Answers

Zeroing weak references require OS X 10.7 or iOS 5.

You can only define weak variables in code, ivars or blocks. AFAIK there is no way to dynamically (at runtime) to create a weak variable because ARC takes effect during compile time. When you run the code it already has the retains and releases added for you.

Having said that you can probably abuse blocks to achieve an effect like this.

Have a block that simply returns the reference.

__weak id weakref = strongref; [weakrefArray addObject:[^{ return weakref; } copy]]; 

Note that you need to copy the block to get it copied to the heap.

Now you can walk the array anytime you like, dealloc'ed objects in blocks will return nil. You can then remove those.

You cannot have code automatically be executed when the weak ref is zeroed. If this is what you want then you can make use of the function of associated objects. Those get deallocated at the same time as the object they are associated to. So you could have your own sentry tag which informs the weak collection about the objects demise.

You would have one associated object to watch for the dealloc (if the association is the only reference) and the associated object would have a pointer to the collection watching. Then in the sentry dealloc you call the weak collection to inform it that the watched object has gone.

Here's my writeup on associated objects: http://www.cocoanetics.com/2012/06/associated-objects/

Here's my implementation:

---- DTWeakCollection.h  @interface DTWeakCollection : NSObject  - (void)checkInObject:(id)object;  - (NSSet *)allObjects;  @end  ---- DTWeakCollection.m  #import "DTWeakCollection.h" #import "DTWeakCollectionSentry.h" #import <objc/runtime.h>  static char DTWeakCollectionSentryKey;  @implementation DTWeakCollection {     NSMutableSet *_entries; }  - (id)init {     self = [super init];     if (self)     {         _entries = [NSMutableSet set];     }     return self; }  - (void)checkInObject:(id)object {     NSUInteger hash = (NSUInteger)object;      // make weak reference     NSNumber *value = [NSNumber numberWithUnsignedInteger:hash];     [_entries addObject:value];      // make sentry     DTWeakCollectionSentry *sentry = [[DTWeakCollectionSentry alloc] initWithWeakCollection:self forObjectWithHash:hash];     objc_setAssociatedObject(object, &DTWeakCollectionSentryKey, sentry, OBJC_ASSOCIATION_RETAIN); }  - (void)checkOutObjectWithHash:(NSUInteger)hash {     NSNumber *value = [NSNumber numberWithUnsignedInteger:hash];     [_entries removeObject:value]; }  - (NSSet *)allObjects {     NSMutableSet *tmpSet = [NSMutableSet set];      for (NSNumber *oneHash in _entries)     {         // hash is actually a pointer to the object         id object = (__bridge id)(void *)[oneHash unsignedIntegerValue];         [tmpSet addObject:object];     }      return [tmpSet copy]; }  @end  ---- DTWeakCollectionSentry.h  #import <Foundation/Foundation.h> @class DTWeakCollection;  @interface DTWeakCollectionSentry : NSObject  - (id)initWithWeakCollection:(DTWeakCollection *)weakCollection forObjectWithHash:(NSUInteger)hash;  @end  --- DTWeakCollectionSentry.m   #import "DTWeakCollectionSentry.h" #import "DTWeakCollection.h"  @interface DTWeakCollection (private)  - (void)checkOutObjectWithHash:(NSUInteger)hash;  @end  @implementation DTWeakCollectionSentry {     __weak DTWeakCollection *_weakCollection;     NSUInteger _hash; }  - (id)initWithWeakCollection:(DTWeakCollection *)weakCollection forObjectWithHash:(NSUInteger)hash {     self = [super init];      if (self)     {         _weakCollection = weakCollection;         _hash = hash;     }      return self; }  - (void)dealloc {     [_weakCollection checkOutObjectWithHash:_hash]; }  @end 

This would be used like this:

NSString *string = @"bla";  @autoreleasepool { _weakCollection = [[DTWeakCollection alloc] init];     [_weakCollection checkInObject:string];  __object = [NSNumber numberWithInteger:1123333];  [_weakCollection checkInObject:__object]; } 

if you output allObjects inside the autorelease pool block then you have two objects in there. Outside you only have the string.

I found that in the dealloc of the entry the object reference is already nil, so you cannot use __weak. Instead I am using the memory address of the object as hash. While these are still in _entries you can treat them as actual object and the allObjects returns an autoreleased array of strong references.

Note: this is not thread safe. Do deal with dealloc's on non-main queues/threads you would need to be careful to synchronize accessing and mutating the internal _entries set.

Note 2: This currently only works with objects checking into a single weak collection since a second check in would overwrite the associated sentry. If you needed this with multiple weak collections then the sentry instead should have an array of those collections.

Note 3: I changed the sentry's reference to the collection to weak as well to avoid a retain cycle.

Note 4: Here are a typedef and helper functions which handle the block syntax for you:

typedef id (^WeakReference)(void);  WeakReference MakeWeakReference (id object) {     __weak id weakref = object;     return [^{ return weakref; } copy]; }  id WeakReferenceNonretainedObjectValue (WeakReference ref) {     if (ref == nil)         return nil;     else         return ref (); } 
like image 150
Cocoanetics Avatar answered Oct 05 '22 18:10

Cocoanetics


Here's code for an a zeroing weak-referencing wrapper class. It works correctly with NSArray, NSSet, and NSDictionary.

The advantage of this solution is that it's compatible with older OS's and that's it simple. The disadvantage is that when iterating, you likely need to verify that -nonretainedObjectValue is non-nil before using it.

It's the same idea as the wrapper in the first part of Cocoanetics' answer, which uses blocks to accomplish the same thing.

WeakReference.h

@interface WeakReference : NSObject {     __weak id nonretainedObjectValue;     __unsafe_unretained id originalObjectValue; }  + (WeakReference *) weakReferenceWithObject:(id) object;  - (id) nonretainedObjectValue; - (void *) originalObjectValue;  @end 

WeakReference.m

@implementation WeakReference  - (id) initWithObject:(id) object {     if (self = [super init]) {         nonretainedObjectValue = originalObjectValue = object;     }     return self; }  + (WeakReference *) weakReferenceWithObject:(id) object {     return [[self alloc] initWithObject:object]; }  - (id) nonretainedObjectValue { return nonretainedObjectValue; } - (void *) originalObjectValue { return (__bridge void *) originalObjectValue; }  // To work appropriately with NSSet - (BOOL) isEqual:(WeakReference *) object {     if (![object isKindOfClass:[WeakReference class]]) return NO;     return object.originalObjectValue == self.originalObjectValue; }  @end 
like image 44
paulmelnikow Avatar answered Oct 05 '22 20:10

paulmelnikow