NSArray of weak references (__unsafe_unretained) to objects under ARC

前端 未结 12 671
南旧
南旧 2020-11-27 11:26

I need to store weak references to objects in an NSArray, in order to prevent retain cycles. I\'m not sure of the proper syntax to use. Is this the correct way?



        
相关标签:
12条回答
  • 2020-11-27 12:07

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

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

    0 讨论(0)
  • 2020-11-27 12:10

    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 iOS 4.x too.

    0 讨论(0)
  • 2020-11-27 12:10

    I've just faced with same problem and found that my before-ARC solution works after 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 00:59:10.266 not_retained_collection_test[28997:907] constructed 2013-06-30 00:59:10.267 not_retained_collection_test[28997:907] addet to set 2013-06-30 00:59:10.581 not_retained_collection_test[28997:907] constructed 2013-06-30 00:59:10.582 not_retained_collection_test[28997:907] addet to set 2013-06-30 00:59:10.881 not_retained_collection_test[28997:907] constructed 2013-06-30 00:59:10.882 not_retained_collection_test[28997:907] addet to set 2013-06-30 00:59:10.883 not_retained_collection_test[28997:907] deallocated 2013-06-30 00:59:10.883 not_retained_collection_test[28997:907] deallocated 2013-06-30 00:59:10.884 not_retained_collection_test[28997:907] deallocated 2013-06-30 00:59:10.885 not_retained_collection_test[28997:907] * -[TestObj respondsToSelector:]: message sent to deallocated instance 0x1f03c8c0

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

    0 讨论(0)
  • 2020-11-27 12:11

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

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

    A "proper" weak property, as 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 the above solutions will comply with behavior #1, they do not exhibit #2.

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

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

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

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

    0 讨论(0)
  • 2020-11-27 12:11

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

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

    0 讨论(0)
  • 2020-11-27 12:13

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

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

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

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

    Thus the "array of weak pointers" problem becomes 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 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 concept (in the light of Tommy's concerns 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 log output:

    2016-09-21 18:11:13.150 foo2[42745:5048189] Thing
    2016-09-21 18:11:13.152 foo2[42745:5048189] (null)
    
    0 讨论(0)
提交回复
热议问题