ARC下归零弱引用的集合

时间:2013-01-08 06:00:31

标签: objective-c cocoa-touch cocoa automatic-ref-counting weak-references

如何在ARC下获得一系列归零弱引用?我不希望数组保留对象。我希望数组元素在被释放时自行删除,或者将这些条目设置为nil。

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

这两个问题涵盖类似的理由:

但两者都没有要求归零参考

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

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

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

8 个答案:

答案 0 :(得分:24)

将弱引用归零需要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 ();
}

答案 1 :(得分:17)

这是一个归零弱引用包装类的代码。它适用于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 :(得分:8)

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

答案 3 :(得分: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

编辑:我根据这个想法从头开始创建sample weak map collection。它有效,但由于以下几个原因它很难看:

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

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

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

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

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

答案 4 :(得分:2)

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

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

答案 5 :(得分: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:

答案 6 :(得分:2)

如果您正在使用至少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 - 之前的&#34;弱&#34;我认为这些东西是用于GC的。直到10.7才支持ARC,所以如果你需要支持那个版本而不是10.6,那真的是一个问题,在这种情况下你需要自己动手(或者使用自定义函数和NSPointerFunctions,然后可以与NSPointerArray,NSHashTable和NSMapTable一起使用。

答案 7 :(得分:0)

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

这个类允许插入nil对象,并且可以选择弱引用它包含的对象(当它们被释放时自动将它们填满)。

自动删除(我试图实现)的问题是你遇到线程安全问题,因为不能保证在哪个时间点对象将被释放,这可能在迭代数组时发生。< / p>

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