[ACCEPTED]-NSArray of weak references (__unsafe_unretained) to objects under ARC-automatic-ref-counting

Accepted answer
Score: 75

As Jason said, you can't make NSArray store weak 10 references. The easiest way to implement 9 Emile's suggestion of wrapping an object 8 inside another object that stores a weak 7 reference to it is the following:

NSValue *value = [NSValue valueWithNonretainedObject:myObj];
[array addObject:value];

Another 6 option: a category that makes NSMutableArray optionally store 5 weak references.

Note that these are "unsafe 4 unretained" references, not self-zeroing 3 weak references. If the array is still around 2 after the objects are deallocated, you'll 1 have a bunch of junk pointers.

Score: 60

The solutions to use a NSValue helper or to create 17 a collection (array, set, dict) object and 16 disable its Retain/Release callbacks are both not 100% failsafe solutions with 15 regard to using ARC.

As various comments 14 to these suggestions point out, such object 13 references will not work like true weak 12 refs:

A "proper" weak property, as 11 supported by ARC, has two behaviors:

  1. Doesn't hold a strong ref to the target object. That means that if the object has no strong references pointing to it, the object will be deallocated.
  2. If the ref'd object is deallocated, the weak reference will become nil.

Now, while 10 the above solutions will comply with behavior 9 #1, they do not exhibit #2.

To get behavior 8 #2 as well, you have to declare your own 7 helper class. It has just one weak property 6 for holding your reference. You then add 5 this helper object to the collection.

Oh, and 4 one more thing: iOS6 and OSX 10.8 supposedly 3 offer a better solution:

[NSHashTable weakObjectsHashTable]
[NSPointerArray weakObjectsPointerArray]
[NSPointerArray pointerArrayWithOptions:]

These should give 2 you containers that hold weak references 1 (but note matt's comments below).

Score: 28

I am new to objective-C, after 20 years 13 of writing c++.

In my view, objective-C 12 is excellent at loosely-coupled messaging, but 11 horrible for data management.

Imagine how 10 happy I was to discover that xcode 4.3 supports 9 objective-c++!

So now I rename all my .m 8 files to .mm (compiles as objective-c++) and 7 use c++ standard containers for data management.

Thus 6 the "array of weak pointers" problem becomes 5 a std::vector of __weak object pointers:

#include <vector>

@interface Thing : NSObject
@end

// declare my vector
std::vector<__weak Thing*> myThings;

// store a weak reference in it
Thing* t = [Thing new];
myThings.push_back(t);

// ... some time later ...

for(auto weak : myThings) {
  Thing* strong = weak; // safely lock the weak pointer
  if (strong) {
    // use the locked pointer
  }
}

Which 4 is equivalent to the c++ idiom:

std::vector< std::weak_ptr<CppThing> > myCppThings;
std::shared_ptr<CppThing> p = std::make_shared<CppThing>();
myCppThings.push_back(p);

// ... some time later ...

for(auto weak : myCppThings) {
  auto strong = weak.lock(); // safety is enforced in c++, you can't dereference a weak_ptr
  if (strong) {
    // use the locked pointer
  }
}

Proof of 3 concept (in the light of Tommy's concerns 2 about vector reallocation):

main.mm:

#include <vector>
#import <Foundation/Foundation.h>

@interface Thing : NSObject
@end

@implementation Thing


@end

extern void foo(Thing*);

int main()
{
    // declare my vector
    std::vector<__weak Thing*> myThings;

    // store a weak reference in it while causing reallocations
    Thing* t = [[Thing alloc]init];
    for (int i = 0 ; i < 100000 ; ++i) {
        myThings.push_back(t);
    }
    // ... some time later ...

    foo(myThings[5000]);

    t = nullptr;

    foo(myThings[5000]);
}

void foo(Thing*p)
{
    NSLog(@"%@", [p className]);
}

example 1 log output:

2016-09-21 18:11:13.150 foo2[42745:5048189] Thing
2016-09-21 18:11:13.152 foo2[42745:5048189] (null)
Score: 13

If you do not require a specific order you 5 could use NSMapTable with special key/value options

NSPointerFunctionsWeakMemory

Uses 4 weak read and write barriers appropriate 3 for ARC or GC. Using NSPointerFunctionsWeakMemory 2 object references will turn to NULL on last 1 release.

Score: 11

I believe the best solution for this is 3 to use NSHashTable or NSMapTable. the Key 2 or/and the Value can be weak. You can read 1 more about it here: http://nshipster.com/nshashtable-and-nsmaptable/

Score: 4

The simplest solution:

NSMutableArray *array = (__bridge_transfer NSMutableArray *)CFArrayCreateMutable(nil, 0, nil);
NSMutableDictionary *dictionary = (__bridge_transfer NSMutableDictionary *)CFDictionaryCreateMutable(nil, 0, nil, nil);
NSMutableSet *set = (__bridge_transfer NSMutableSet *)CFSetCreateMutable(nil, 0, nil);

Note: And this works on 1 iOS 4.x too.

Score: 4

To add weak self reference to NSMutableArray, create 6 a custom class with a weak property as given 5 below.

NSMutableArray *array = [NSMutableArray new];

Step 1: create a custom class 

@interface DelegateRef : NSObject

@property(nonatomic, weak)id delegateWeakReference;

@end

Step 2: create a method to add self as weak reference to NSMutableArray. But here we add the DelegateRef object

-(void)addWeakRef:(id)ref
{

  DelegateRef *delRef = [DelegateRef new];

  [delRef setDelegateWeakReference:ref] 

  [array addObject:delRef];

}

Step 3: later on, if the property 4 delegateWeakReference == nil, the object can be removed from the array 3

The property will be nil, and the references 2 will be deallocated at proper time independent 1 of this array references

Score: 3

No, that's not correct. Those aren't actually 9 weak references. You can't really store 8 weak references in an array right now. You 7 need to have a mutable array and remove 6 the references when you're done with them 5 or remove the whole array when you're done 4 with it, or roll your own data structure 3 that supports it.

Hopefully this is something 2 that they'll address in the near future 1 (a weak version of NSArray).

Score: 2

I've just faced with same problem and found 15 that my before-ARC solution works after 14 converting with ARC as designed.

// function allocates mutable set which doesn't retain references.
NSMutableSet* AllocNotRetainedMutableSet() {
    CFMutableSetRef setRef = NULL;
    CFSetCallBacks notRetainedCallbacks = kCFTypeSetCallBacks;
    notRetainedCallbacks.retain = NULL;
    notRetainedCallbacks.release = NULL;
    setRef = CFSetCreateMutable(kCFAllocatorDefault,
    0,
    &notRetainedCallbacks);
    return (__bridge NSMutableSet *)setRef;
}

// test object for debug deallocation
@interface TestObj : NSObject
@end
@implementation TestObj
- (id)init {
   self = [super init];
   NSLog(@"%@ constructed", self);
   return self;
}
- (void)dealloc {
   NSLog(@"%@ deallocated", self);
}
@end


@interface MainViewController () {
   NSMutableSet *weakedSet;
   NSMutableSet *usualSet;
}
@end

@implementation MainViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
      weakedSet = AllocNotRetainedMutableSet();
      usualSet = [NSMutableSet new];
   }
    return self;
}

- (IBAction)addObject:(id)sender {
   TestObj *obj = [TestObj new];
   [weakedSet addObject:obj]; // store unsafe unretained ref
   [usualSet addObject:obj]; // store strong ref
   NSLog(@"%@ addet to set", obj);
   obj = nil;
   if ([usualSet count] == 3) {
      [usualSet removeAllObjects];  // deallocate all objects and get old fashioned crash, as it was required.
      [weakedSet enumerateObjectsUsingBlock:^(TestObj *invalidObj, BOOL *stop) {
         NSLog(@"%@ must crash here", invalidObj);
      }];
   }
}
@end

Output:

2013-06-30 13 00:59:10.266 not_retained_collection_test[28997:907] constructed 12 2013-06-30 00:59:10.267 not_retained_collection_test[28997:907] addet 11 to set 2013-06-30 00:59:10.581 not_retained_collection_test[28997:907] constructed 10 2013-06-30 00:59:10.582 not_retained_collection_test[28997:907] addet 9 to set 2013-06-30 00:59:10.881 not_retained_collection_test[28997:907] constructed 8 2013-06-30 00:59:10.882 not_retained_collection_test[28997:907] addet 7 to set 2013-06-30 00:59:10.883 not_retained_collection_test[28997:907] deallocated 6 2013-06-30 00:59:10.883 not_retained_collection_test[28997:907] deallocated 5 2013-06-30 00:59:10.884 not_retained_collection_test[28997:907] deallocated 4 2013-06-30 00:59:10.885 not_retained_collection_test[28997:907] * -[TestObj respondsToSelector:]: message 3 sent to deallocated instance 0x1f03c8c0

Checked 2 with iOS versions 4.3, 5.1, 6.2. Hope it 1 will be useful to somebody.

Score: 1

If you need zeroing weak references, see this answer for code 4 you can use for a wrapper class.

Other answers 3 to that question suggest a block-based wrapper, and ways 2 to automatically remove zeroed elements 1 from the collection.

Score: 1

If you use a lot this comportment it's indicated 4 to your own NSMutableArray class (subclass 3 of NSMutableArray) which doesn't increase 2 the retain count.

You should have something 1 like this:

-(void)addObject:(NSObject *)object {
    [self.collection addObject:[NSValue valueWithNonretainedObject:object]];
}

-(NSObject*) getObject:(NSUInteger)index {

    NSValue *value = [self.collection objectAtIndex:index];
    if (value.nonretainedObjectValue != nil) {
        return value.nonretainedObjectValue;
    }

    //it's nice to clean the array if the referenced object was deallocated
    [self.collection removeObjectAtIndex:index];

    return nil;
}

More Related questions