ARC下归零弱引用的集合

我怎样才能得到一个数组零引用弱引用 ? 我不希望数组保留对象。 而且我希望数组元素可以在释放时自行删除,或者将这些条目设置为nil。

同样的,我怎样才能用字典呢? 我不希望字典保留这些值。 再次,我希望字典元素或者在值被释放时自行移除,或者将值设置为零。 (我需要保留这些唯一标识符的键,至less在取消分配相应的值之前)。

这两个问题包含了类似的理由:

  • 对ARC下的对象的弱引用的NSArray
  • 有一个不保留的id对象列表?

但是也不要求引用归零

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

我看到的唯一的解决scheme是创build我自己的 NSValue类包装类(weak)属性, 这个答案提到,接近尾声 。 有没有更好的方法,我没有看到?

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

清零弱引用需要OS X 10.7或iOS 5。

您只能在代码,ivars或块中定义弱variables。 AFAIK没有办法dynamic地(在运行时)创build一个弱variables,因为ARC在编译时生效。 当您运行代码时,它已经为您添加了保留和发布。

话虽如此,你可能会滥用块来达到这样的效果。

有一个简单的返回参考的块。

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

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

现在你可以随时随地走arrays,分块的对象将返回零。 你可以删除这些。

弱引用清零时,不能自动执行代码。 如果这是你想要的,那么你可以利用关联对象的function。 那些与它们关联的对象同时被释放。 所以你可以有自己的哨兵标签,告诉弱关于物品灭亡的收集。

您将有一个关联的对象来监视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]; } 

如果你输出autorelease池块内的allObjects,那么你有两个对象在那里。 外面你只有string。

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

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

注2:目前只适用于检查单个弱集合的对象,因为第二次检查会覆盖相关的哨兵。 如果你需要多个弱集合,那么哨兵应该有一个这样的集合数组。

注3:我把哨兵对collections品的引用改为弱,以避免保留周期。

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

这是一个调零弱引用包装类的代码。 它与NSArray,NSSet和NSDictionary正常工作。

这个解决scheme的优点是它与旧版本的操作系统兼容,这很简单。 缺点是在迭代时,在使用它之前可能需要validation-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 

NSMapTable应该为你工作。 在iOS 6中可用。

 @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 

编辑 :我基于这个想法从头开始创build一个示例弱地图集合 。 它的工作,但由于几个原因,这是丑陋的:

我在NSObject上使用了一个类别为key,next map bucket和拥有该对象的集合添加了@properties。

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

但是,为了使地图具有dynamic容量,需要接收更新元素的数量来计算负载因子并在需要时扩展容量。 也就是说,除非每次添加元素时都要执行迭代整个数组的Θ(n)更新。 我用一个callback函数对我添加到集合中的示例对象的dealloc方法做了这个。 我可以编辑原始对象(为了简洁起见)或者从超类inheritance,或者调用dealloc。 无论如何,丑陋。

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

最后,请注意,该项目的testing是最小的,我可以忽略一些东西。

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

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

只需使用以下代码为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创build一个类别。

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

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

那么,你应该做的是调用addWeakReference:为你的容器类。

如果您至less使用MacOS X 10.5或iOS6,则:

  • NSPointerArray weakObjectsPointerArray / pointerArrayWithWeakObjects是NSArray的弱引用标准
  • NSHashTable hashTableWithWeakObjects / weakObjectsHashTable是NSSet的弱引用标准
  • NSMapTable是NSDictionary的弱引用标准(可能有弱键和/或弱值)

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

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