41

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

同样,我怎样才能用字典做到这一点?我不希望字典保留这些值。同样,我希望字典元素在释放值时自行删除,或者将值设置为 nil。(我需要保留作为唯一标识符的键,至少在相应的值被释放之前。)

这两个问题涵盖了相似的基础:

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

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

我看到的唯一解决方案是创建我自己的具有属性的类似 NSValue 的包装器类(weak),正如这个答案提到的,接近 end。有没有更好的方法我没有看到?

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

4

8 回答 8

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,那么您将在其中有两个对象。外面你只有字符串。

我发现在entry的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 ();
}
于 2013-01-08T06:38:10.090 回答
17

这是归零弱引用包装类的代码。它适用于 NSArray、NSSet 和 NSDictionary。

此解决方案的优点是它与旧操作系统兼容,而且很简单。缺点是在迭代时,您可能需要-nonretainedObjectValue在使用它之前验证它是非零的。

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

弱引用.h

@interface WeakReference : NSObject {
    __weak id nonretainedObjectValue;
    __unsafe_unretained id originalObjectValue;
}

+ (WeakReference *) weakReferenceWithObject:(id) object;

- (id) nonretainedObjectValue;
- (void *) originalObjectValue;

@end

弱引用.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
于 2013-01-08T16:28:42.640 回答
8

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

于 2013-04-03T19:29:07.547 回答
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;
    }
}

Output:

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

edit: I created a sample weak map collection from scratch based on this idea. It works, but it's ugly for several reasons:

I used a category on NSObject to add @properties for the key,next map bucket, and a reference to the collection owning the object.

Once you nil the object it disappears from the collection.

BUT, for the map to have a dynamic capacity, it needs to receive an update the number of elements to calculate the load factor and expand capacity if needed. That is, unless you want to perform a Θ(n) update iterating the whole array each time you add an element. I did this with a callback on the dealloc method of the sample object I'm adding to the collection. I could edit the original object (which I did for brevity) or inherit from a superclass, or swizzle the dealloc. In any case, ugly.

However, if you don't mind having a fixed capacity collection, you don't need the callbacks. The collection uses separate chaining and assuming an uniform distribution of the hash function, performance will be Θ(1+n/m) being n=elements,m=capacity. But (more buts) to avoid breaking the chaining you would need to add a previous link as a category @property and link it to the next element in the dealloc of the element. And once we are touching the dealloc, it's just as good to notify the collection that the element is being removed (which is what is doing now).

Finally, note that the test in the project is minimal and I could have overlooked something.

于 2013-01-08T09:03:09.027 回答
3

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

  • NSPointerArray weakObjectsPointerArray/pointerArrayWithWeakObjects 是 NSArray 的弱引用代表
  • NSHashTable hashTableWithWeakObjects/weakObjectsHashTable 是 NSSet 的弱引用代表
  • NSMapTable 是 NSDictionary 的弱参考代表(可以有弱键和/或弱值)

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

编辑:我看到原始海报询问了 ARC。我认为在这些容器可以与 ARC 一起使用之前确实是 10.8 和 iOS 6——我认为以前的“弱”东西是针对 GC 的。ARC 直到 10.7 才被支持,所以如果您需要支持该版本而不是 10.6,这确实是一个问题,在这种情况下,您需要推出自己的(或者可能使用带有 NSPointerFunctions 的自定义函数,然后可以反过来使用NSPointerArray、NSHashTable 和 NSMapTable)。

于 2015-07-09T20:48:13.500 回答
2

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

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

于 2013-01-09T10:56:12.787 回答
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:为您的容器类调用。

于 2013-12-24T12:11:45.137 回答
0

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

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

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

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

于 2017-11-24T07:45:05.390 回答