首页 文章

ARC下归零弱引用的集合

提问于
浏览
40

如何在ARC下获得 zeroing weak references 数组?我不喜欢数组元素要么在取消分配时删除它们,要么将这些条目设置为nil .

同样,我怎么能用字典做到这一点?我不希望字典保留值 . 再次,我希望字典元素要么在取消分配值时删除自己,要么将值设置为nil . (我需要保留密钥,这是唯一的标识符,至少在相应的值被释放之前 . )

这两个问题涉及类似的问题:

但两者都没有要求 zeroing references .

根据文档,NSPointerArray和NSHashMap都不支持ARC下的弱引用 . NSValue的nonretainedObjectValue也不起作用,因为它是非归零的 .

我看到的唯一解决方案是使用 (weak) 属性创建我自己的类似NSValue的包装类,如this answer mentions, near the end . 有没有更好的方式我没有看到?

我正在为OS X 10.7和iOS 6.0开发 .

8 回答

  • 7

    归零弱引用需要OS X 10.7或iOS 5 .

    您只能在代码,ivars或块中定义弱变量 . AFAIK无法动态(在运行时)创建弱变量,因为ARC在编译期间生效 . 当您运行代码时,它已经为您添加了保留和版本 .

    说过你可能会滥用块来实现这样的效果 .

    有一个只返回引用的块 .

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

    请注意,您需要复制块以将其复制到堆中 .

    现在你可以随时随地走数组,块中的dealloc'ed对象将返回nil . 然后你可以删除它们 .

    当弱参考归零时,您不能自动执行代码 . 如果这是您想要的,那么您可以使用相关对象的功能 . 那些与它们关联的对象同时被释放 . 所以你可以拥有自己的哨兵标签,告知弱者收集对象消亡 .

    您将有一个关联对象来监视dealloc(如果关联是唯一的引用),并且关联的对象将具有指向集合观察的指针 . 然后在哨兵dealloc中你调用弱集合通知它观察对象已经消失 .

    这是我对相关对象的写作:http://www.cocoanetics.com/2012/06/associated-objects/

    这是我的实现:

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

    这将使用这样:

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

    如果在自动释放池块中输出allObjects,那么你有两个对象 . 外面你只有字符串 .

    我发现在条目的dealloc中对象引用已经是nil,所以你不能使用__weak . 相反,我使用对象的内存地址作为哈希 . 虽然这些仍然在_entries中,但您可以将它们视为实际对象,而allObjects将返回一个自动释放的强引用数组 .

    注意:这不是线程安全的 . 处理非主要队列/线程上的dealloc,你需要小心同步访问和改变内部_entries集 .

    注意2:这当前仅适用于检查单个弱集合的对象,因为第二次检入会覆盖关联的哨兵 . 如果你需要多个弱集合,那么哨兵应该有一个这些集合的数组 .

    注3:我将哨兵对集合的引用改为弱,以避免保留周期 .

    注4:这是一个typedef和helper函数,它们为您处理块语法:

    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 ();
    }
    
  • 2

    这是一个归零弱引用包装类的代码 . 它适用于NSArray,NSSet和NSDictionary .

    这个解决方案的优点是它很简单 . 缺点是在迭代时,您可能需要在使用之前验证 -nonretainedObjectValue 是否为非零 .

    它与Cocoanetics的第一部分中的包装器的想法相同,它使用块来完成同样的事情 .

    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
    
  • 2

    NSMapTable 应该适合你 . 适用于iOS 6 .

  • 3
    @interface Car : NSObject
    @end
    @implementation Car
    -(void) dealloc {
        NSLog(@"deallocing");
    }
    @end
    
    
    int main(int argc, char *argv[])
    {
        @autoreleasepool {
            Car *car = [Car new];
    
            NSUInteger capacity = 10;
            id __weak *_objs = (id __weak *)calloc(capacity,sizeof(*_objs));
            _objs[0] = car;
            car = nil;
    
            NSLog(@"%p",_objs[0]);
            return EXIT_SUCCESS;
        }
    }
    

    输出:

    2013-01-08 10:00:19.171 X[6515:c07] deallocing
    2013-01-08 10:00:19.172 X[6515:c07] 0x0
    

    edit :我基于这个想法从零开始创建了一个sample weak map collection . 它有效,但由于以下几个原因它很难看:

    我使用category on NSObject为密钥添加@properties,下一个映射存储桶以及对拥有该对象的集合的引用 .

    一旦你没有对象,它就会从集合中消失 .

    但是, Map 要有动态容量,它需要接收更新元素的数量,以计算负载因子,并在需要时扩展容量 . 也就是说,除非您希望每次添加元素时执行Θ(n)更新迭代整个数组 . 我通过对我正在添加到集合的示例对象的dealloc方法进行回调来完成此操作 . 我可以编辑原始对象(我为了简洁起见)或从超类继承,或者调用dealloc . 无论如何,丑陋 .

    但是,如果你不需要回调 . 该集合使用separate chaining并且假设散列函数的均匀分布,性能将是Θ(1 n / m),其中n =元素,m =容量 . 但是(更多但是)要避免破坏链接,您需要将以前的链接添加为类别@property并将其链接到元素的dealloc中的下一个元素 . 一旦我们触及dealloc,通知集合该元素被删除(这就是现在正在做的事情)同样好 .

    最后,请注意项目中的测试很少,我可能忽略了一些东西 .

  • 0

    我只是创建NSMutableDictionary和NSMutableSet的非线程安全弱ref版本 . 代码在这里:https://gist.github.com/4492283

    对于NSMutableArray,事情更复杂,因为它不能包含 nil ,并且可能会多次将对象添加到数组中 . 但实施一个是可行的 .

  • 2

    只需为NSMutableSet添加一个类别,其代码如下:

    @interface WeakReferenceObj : NSObject
    @property (nonatomic, weak) id weakRef;
    @end
    
    @implementation WeakReferenceObj
    + (id)weakReferenceWithObj:(id)obj{
        WeakReferenceObj *weakObj = [[WeakReferenceObj alloc] init];
        weakObj.weakRef = obj;
        return weakObj;
    }
    @end
    
    @implementation NSMutableSet(WeakReferenceObj)
    - (void)removeDeallocRef{
        NSMutableSet *deallocSet = nil;
        for (WeakReferenceObj *weakRefObj in self) {
            if (!weakRefObj.weakRef) {
                if (!deallocSet) {
                    deallocSet = [NSMutableSet set];
                }
                [deallocSet addObject:weakRefObj];
            }
        }
        if (deallocSet) {
            [self minusSet:deallocSet];
        }
    }
    
    - (void)addWeakReference:(id)obj{
        [self removeDeallocRef];
        [self addObject:[WeakReferenceObj weakReferenceWithObj:obj]];
    }
    @end
    

    为NSMutableArray和NSMutableDictionary创建类别的方法相同 .

    删除didReceiveMemoryWarning中的dealloc引用会更好 .

    - (void)didReceiveMemoryWarning{
        [yourWeakReferenceSet removeDeallocRef];
    }
    

    然后,您应该做的是为容器类调用 addWeakReference: .

  • 24

    如果您正在使用至少MacOS X 10.5或iOS6,那么:

    • NSPointerArray weakObjectsPointerArray / pointerArrayWithWeakObjects是NSArray的弱引用标准

    • NSHashTable hashTableWithWeakObjects / weakObjectsHashTable是NSSet的弱引用标准

    • NSMapTable是NSDictionary的弱引用(可以有弱键和/或弱值)

    请注意,集合可能不会立即注意到对象已经消失,因此计数仍然可能更高,并且即使关联的对象消失,键也可能仍然存在,等等.NSPointerArray有一个-compact方法,理论上它应该摆脱任何nilled指针 . NSMapTable文档指出,weakToStrong映射的键将保留在表中(即使实际上是nil),直到它被调整大小,这意味着即使不再逻辑引用,强对象指针也可以保留在内存中 .

    编辑:我看到有关ARC的原始海报 . 我认为在这些容器可以与ARC一起使用之前确实是10.8和iOS 6 - 我认为以前的“弱”用于GC . 直到10.7才支持ARC,所以如果你需要支持那个版本而不是10.6就是一个问题,在这种情况下你需要自己动手(或者可能使用带有NSPointerFunctions的自定义函数,然后可以使用它们使用NSPointerArray,NSHashTable和NSMapTable) .

  • 16

    请参阅BMNullableArray类,它是我的BMCommons框架的一部分,可以完全解决此问题 .

    此类允许插入nil对象,并且可以选择弱引用它包含的对象(当它们被取消分配时自动对它们进行nilling) .

    自动删除(我试图实现)的问题是你得到了线程安全问题,因为不能保证在哪个时间点对象将被解除分配,这可能在迭代数组时发生 .

    这个类是对NSPointerArray的改进,因为它为您提取了一些较低级别的细节,并允许您使用对象而不是指针 . 它甚至支持NSFastEnumeration在那里使用nil引用迭代数组 .

相关问题