NSArray of weak references (__unsafe_unretained) t

2019-01-05 10:20发布

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?

Foo* foo1 = [[Foo alloc] init];
Foo* foo2 = [[Foo alloc] init];

__unsafe_unretained Foo* weakFoo1 = foo1;
__unsafe_unretained Foo* weakFoo2 = foo2;

NSArray* someArray = [NSArray arrayWithObjects:weakFoo1, weakFoo2, nil];

Note that I need to support iOS 4.x, thus the __unsafe_unretained instead of __weak.


EDIT (2015-02-18):

For those wanting to use true __weak pointers (not __unsafe_unretained), please check out this question instead: Collections of zeroing weak references under ARC

12条回答
\"骚年 ilove
2楼-- · 2019-01-05 10:38

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).

查看更多
闹够了就滚
3楼-- · 2019-01-05 10:41

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.

查看更多
我只想做你的唯一
4楼-- · 2019-01-05 10:44

I think an elegant solution is what Mr. Erik Ralston propose on his Github repository

https://gist.github.com/eralston/8010285

this are the essential steps:

create a category for NSArray and NSMutableArray

in the implementation create a convenience class with a weak property. Your category will assign the objects to this weak property.

.h

 #import <Foundation/Foundation.h>

@interface NSArray(WeakArray)

- (__weak id)weakObjectForIndex:(NSUInteger)index;
-(id<NSFastEnumeration>)weakObjectsEnumerator;

@end

@interface NSMutableArray (FRSWeakArray)

-(void)addWeakObject:(id)object;
-(void)removeWeakObject:(id)object;

-(void)cleanWeakObjects;

@end

.m

#import "NSArray+WeakArray.h"

@interface WAArrayWeakPointer : NSObject
@property (nonatomic, weak) NSObject *object;
@end

@implementation WAArrayWeakPointer
@end

@implementation NSArray (WeakArray)


-(__weak id)weakObjectForIndex:(NSUInteger)index
{
    WAArrayWeakPointer *ptr = [self objectAtIndex:index];
    return ptr.object;
}

-(WAArrayWeakPointer *)weakPointerForObject:(id)object
{
    for (WAArrayWeakPointer *ptr in self) {
        if(ptr) {
            if(ptr.object == object) {
                return ptr;
            }
        }
    }

    return nil;
}

-(id<NSFastEnumeration>)weakObjectsEnumerator
{
    NSMutableArray *enumerator = [[NSMutableArray alloc] init];
    for (WAArrayWeakPointer *ptr in self) {
        if(ptr && ptr.object) {
            [enumerator addObject:ptr.object];
        }
    }
    return enumerator;
}

@end

@implementation NSMutableArray (FRSWeakArray)

-(void)addWeakObject:(id)object
{
    if(!object)
        return;

    WAArrayWeakPointer *ptr = [[WAArrayWeakPointer alloc] init];
    ptr.object = object;
    [self addObject:ptr];

    [self cleanWeakObjects];
}

-(void)removeWeakObject:(id)object
{
    if(!object)
        return;

    WAArrayWeakPointer *ptr = [self weakPointerForObject:object];

    if(ptr) {

        [self removeObject:ptr];

        [self cleanWeakObjects];
    }
}

-(void)cleanWeakObjects
{

    NSMutableArray *toBeRemoved = [[NSMutableArray alloc] init];
    for (WAArrayWeakPointer *ptr in self) {
        if(ptr && !ptr.object) {
            [toBeRemoved addObject:ptr];
        }
    }

    for(WAArrayWeakPointer *ptr in toBeRemoved) {
        [self removeObject:ptr];
    }
}

@end
查看更多
放荡不羁爱自由
5楼-- · 2019-01-05 10:47

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)
查看更多
再贱就再见
6楼-- · 2019-01-05 10:48

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

查看更多
一纸荒年 Trace。
7楼-- · 2019-01-05 10:51

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

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

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

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

查看更多
登录 后发表回答