对 ARC 下对象的弱引用 (__unsafe_unretained) 的 NSArray

Posted

技术标签:

【中文标题】对 ARC 下对象的弱引用 (__unsafe_unretained) 的 NSArray【英文标题】:NSArray of weak references (__unsafe_unretained) to objects under ARC 【发布时间】:2012-03-09 07:51:17 【问题描述】:

我需要在 NSArray 中存储对对象的弱引用,以防止保留循环。我不确定要使用的正确语法。这是正确的方法吗?

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

请注意,我需要支持 iOS 4.x,因此使用 __unsafe_unretained 而不是 __weak


编辑(2015-02-18):

对于那些想要使用真正的__weak 指针(不是__unsafe_unretained)的人,请查看这个问题:Collections of zeroing weak references under ARC

【问题讨论】:

“我可怜这个弱小的傻瓜!” 我建议不要使用 NSPointerArray 和 NSPointerFunctionsWeakMemory NSPointerFunctionOption @leviathan:这个问题是在 ios 6 出来之前被问到的。 我创建了this 作为字典,将对象存储为有效地将弱引用归零。它可以被修改(和清理)以满足您的目的。 【参考方案1】:

不,这是不正确的。这些实际上并不是弱引用。您现在不能真正将弱引用存储在数组中。您需要有一个可变数组并在完成后删除引用,或者在完成后删除整个数组,或者滚动您自己的支持它的数据结构。

希望他们会在不久的将来解决这个问题(NSArray 的弱版本)。

【讨论】:

如果我将一个指向Foo__unsafe_unretained 指针包装在另一个可以由NSArray 保留的对象中怎么办? @EmileCormier 你可以这样做,但你应该让它成为一个弱属性,而不是__unsafe_unretained。这将阻止保留周期,但您很容易最终访问垃圾。 我希望我可以使用一个弱属性,但我不能忽略我的应用程序的 iOS 4.x 市场。我的背景是 C/C++,所以在 shared_ptr 之前的糟糕日子里我习惯了玩火。 :-) 根据您的应用程序发布的时间,您实际上可能想忽略 iOS 4! :) 如果它遵循与 iOS 4 相同的趋势,iOS 5 发布 6 个月后应该会在大约 90% 的设备中使用,而这个标记是在 4 月,因此支持 iOS 4 的额外工作可能无法支付花在编码/调试上的时间。 刚刚有了一个想法...我可以使用扩展为__unsafe_unretained__weak 的宏,而不是使用__unsafe_unretained,具体取决于基本SDK 版本。这样,我可以在 iOS 5 设备上的开发过程中检测到悬空指针问题。【参考方案2】:

正如 Jason 所说,你不能让 NSArray 存储弱引用。实现 Emile 的建议的最简单的方法是将一个对象包装在另一个存储弱引用的对象中:

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

另一种选择:category 使NSMutableArray 可选择存储弱引用。

请注意,这些是“不安全的未保留”引用,而不是自归零弱引用。如果在对象被释放后数组仍然存在,你就会有一堆垃圾指针。

【讨论】:

我的荣幸!另外,请查看我的更新答案以获取另一个选项。 我认为NSMutableArray 上的类别是个坏主意,因为它是一个类集群,你会遇到很多问题。最好创建自己的 NSObject 子类,它具有与 NSMutableArray 相同的方法。 您查看过我链接到的实际解决方案吗?我不是 NSMutableArray 和类集群方面的专家,但它似乎正在设置一个使用特定后备存储的额外工厂方法,同时不影响任何其他类方法。 注意:这些不是自归零弱引用。他们只是没有得到保留。 是的,正如@mxcl 所说,这些是不安全的未保留的,而不是弱的。因此,如果您在释放后尝试解开该值,它将崩溃。请改用 NSMapTable 和 NSHashTable。【参考方案3】:

使用 NSValue 助手或创建集合(数组、集合、字典)对象和disable its Retain/Release callbacks 的解决方案都不是 100% 使用 ARC 的故障安全解决方案。

正如各种 cmet 对这些建议所指出的那样,这样的对象引用不会像真正的弱引用那样工作:

ARC 支持的“适当的”弱属性有两种行为:

    不持有对目标对象的强引用。这意味着如果对象没有指向它的强引用,则该对象将被释放。 如果引用的对象被释放,弱引用将变为 nil。

现在,虽然上述解决方案将符合行为 #1,但它们不会表现出 #2。

要获得行为 #2,您必须声明自己的帮助程序类。它只有一个弱属性可供您参考。然后将此辅助对象添加到集合中。

哦,还有一件事:iOS6 和 OSX 10.8 据说提供了更好的解决方案:

[NSHashTable weakObjectsHashTable]
[NSPointerArray weakObjectsPointerArray]
[NSPointerArray pointerArrayWithOptions:]

这些应该为您提供包含弱引用的容器(但请注意下面的 matt 的 cmets)。

【讨论】:

[NSPointer weakObjectsPointerArray] 在 iOS 6 上仍然是 not ARC __weak 引用。所以这不是更好的解决方案:就像您批评的解决方案一样,它符合行为#1 但不符合#2。它既优雅又方便,但不是 ARC __weak 引用。 @matt 您是如何发现weakObjectsPointerArray 不返回__weak refs 的?您是否在代码中对其进行了测试,或者您是否参考“NSPointerArray 类参考”文档说“重要提示:NSPointerArray 不支持自动引用计数(ARC)下的弱引用。”?我想知道这是否不仅仅是 10.8 之前的遗留问题,而且是一个文档错误。 好吧,我从没想过盒子里的一个巨大的警告会“只是一个剩菜”。引用显然很弱,并且当对象被释放时它们显然被设为 NULL,但我不知道如何测试它们在完整的 ARC 意义上是__weak。我会问的。 如果它们在对象被释放时变为 NULL,那么它们符合我的行为 #2,然后一切都很好。在 ARC 之前,弱引用只是一个指针(类型 id),一旦其引用的对象被释放,它就会悬空。 ARC 的弱 ref 更智能,因为它随后变为 NULL,使其使用起来更安全。因此,如果这是您看到的行为,那么文档中的警告框确实是一个错误。我还发送了关于此的反馈,要求他们仔细检查。不幸的是,他们(Apple)没有给你反馈:( 顺便说一句,如果您查看+ (NSHashTable *)weakObjectsHashTable;NSHashTable 标头,您会发现一条评论:// entries are not necessarily purged right away when the weak object is reclaimed【参考方案4】:

如果您需要归零弱引用,请参阅this answer 了解可用于包装类的代码。

that question 的其他答案建议使用基于块的包装器,以及从集合中自动删除零元素的方法。

【讨论】:

【参考方案5】:

在编写 c++ 20 年后,我是 Objective-C 的新手。

在我看来,Objective-C 在松耦合消息传递方面表现出色,但在数据管理方面却很糟糕。

想象一下我是多么高兴地发现 xcode 4.3 支持 Objective-c++!

所以现在我将所有 .m 文件重命名为 .mm(编译为 Objective-C++)并使用 C++ 标准容器进行数据管理。

因此“弱指针数组”问题变成了 __weak 对象指针的 std::vector:

#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
  

相当于c++的成语:

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
  

概念证明(鉴于 Tommy 对向量重新分配的担忧):

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]);

示例日志输出:

2016-09-21 18:11:13.150 foo2[42745:5048189] Thing
2016-09-21 18:11:13.152 foo2[42745:5048189] (null)

【讨论】:

是的,但这不是真正的客观 C 解决方案。你只是喜欢它,因为你是一个 C++ 人。 Aran,我是一个寻求使用最好的可用工具来完成工作的人。 Objective-c 没有很好的数据处理能力,本质上是 C 语言,带有一些消息技巧和庞大的支持库。这是一种不合时宜且过时的语言(因此苹果寻求用 SWIFT 取代它)。既然我们有机会使用 xcode 自带的优秀 c++ 编译器,何不接受呢? 这不是Objective-c,就像Objective-C不是C一样。它被编译器原生支持并且具有更好的内存管理能力 我将所有objective-c 对象包装在轻量级c++ 包装器中。它提供了安全的代码并完全消除了 Apple 最新的白痴 - Swift。 我觉得这只能证明可能的安全性——假设最终的基本现实与realloc 相同,调整大小并不一定意味着移动。我想我们还需要检查.data()&amp;myThings[0] 在插入第一件事之后和插入最后一个之前是否发生了变化?我为如此不具建设性而道歉——我是在工作中打字的,并为使用时间做出了微不足道的理由。我是认为可能有问题的人,如果您确定不是,请不要浪费更多时间。我可以在闲暇时证明自己是错误的(并承诺会尝试这样做)。【参考方案6】:

我刚刚遇到同样的问题,发现我的 before-ARC 解决方案在按照设计使用 ARC 进行转换后可以正常工作。

// function allocates mutable set which doesn't retain references.
NSMutableSet* AllocNotRetainedMutableSet() 
    CFMutableSetRef setRef = NULL;
    CFSetCallBacks notRetainedCallbacks = kCFTypeSetCallBacks;
    notRetainedCallbacks.retain = NULL;
    notRetainedCallbacks.release = NULL;
    setRef = CFSetCreateMutable(kCFAllocatorDefault,
    0,
    &notRetainedCallbacks);
    return (__bridge NSMutableSet *)setRef;


// test object for debug deallocation
@interface TestObj : NSObject
@end
@implementation TestObj
- (id)init 
   self = [super init];
   NSLog(@"%@ constructed", self);
   return self;

- (void)dealloc 
   NSLog(@"%@ deallocated", self);

@end


@interface MainViewController () 
   NSMutableSet *weakedSet;
   NSMutableSet *usualSet;

@end

@implementation MainViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) 
        // Custom initialization
      weakedSet = AllocNotRetainedMutableSet();
      usualSet = [NSMutableSet new];
   
    return self;


- (IBAction)addObject:(id)sender 
   TestObj *obj = [TestObj new];
   [weakedSet addObject:obj]; // store unsafe unretained ref
   [usualSet addObject:obj]; // store strong ref
   NSLog(@"%@ addet to set", obj);
   obj = nil;
   if ([usualSet count] == 3) 
      [usualSet removeAllObjects];  // deallocate all objects and get old fashioned crash, as it was required.
      [weakedSet enumerateObjectsUsingBlock:^(TestObj *invalidObj, BOOL *stop) 
         NSLog(@"%@ must crash here", invalidObj);
      ];
   

@end

输出:

2013-06-30 00:59:10.266 not_retained_collection_test[28997:907] 建造时间 2013-06-30 00:59:10.267 not_retained_collection_test[28997:907] 添加到 设置 2013-06-30 00:59:10.581 not_retained_collection_test[28997:907] 建造时间 2013-06-30 00:59:10.582 not_retained_collection_test[28997:907] 添加到 设置 2013-06-30 00:59:10.881 not_retained_collection_test[28997:907] 2013-06-30 00:59:10.882 建造 not_retained_collection_test[28997:907] 添加到 设置 2013-06-30 00:59:10.883 not_retained_collection_test[28997:907] 解除分配 2013-06-30 00:59:10.883 not_retained_collection_test[28997:907] 解除分配 2013-06-30 00:59:10.884 not_retained_collection_test[28997:907] 解除分配 2013-06-30 00:59:10.885 not_retained_collection_test[28997:907] * -[TestObj respondsToSelector:]: 消息发送到释放的实例 0x1f03c8c0

检查了 iOS 4.3、5.1、6.2 版本。 希望它对某人有用。

【讨论】:

【参考方案7】:

我相信最好的解决方案是使用 NSHashTable 或 NSMapTable。键或/和值可能很弱。你可以在这里阅读更多信息:http://nshipster.com/nshashtable-and-nsmaptable/

【讨论】:

【参考方案8】:

如果您不需要特定的订单,您可以使用带有特殊键/值选项的 NSMapTable

NSPointerFunctionsWeakMemory

使用适用于 ARC 或 GC 的弱读写屏障。使用 NSPointerFunctionsWeakMemory 对象引用将在上次发布时变为 NULL。

【讨论】:

这是正确的答案,如果您可以容忍类似集合(而不是类似数组)的行为【参考方案9】:

最简单的解决方案:

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

注意:这也适用于 iOS 4.x。

【讨论】:

不清楚是什么让这些持有弱对象。 零分配器。查看此方法的声明 CFMutableArrayRef CFArrayCreateMutable ( CFAllocatorRef allocator, CFIndex capacity, const CFArrayCallBacks *callBacks ); 您可以在文档中阅读更多内容。 问题要求__unsafe_unretained,这正确提供__unsafe_unretained;不过,如果您不遵循原作者对weak 一词的滥用,那就太好了。【参考方案10】:

要添加对 NSMutableArray 的弱自引用,请创建一个具有以下给出的弱属性的自定义类。

NSMutableArray *array = [NSMutableArray new];

Step 1: create a custom class 

@interface DelegateRef : NSObject

@property(nonatomic, weak)id delegateWeakReference;

@end

Step 2: create a method to add self as weak reference to NSMutableArray. But here we add the DelegateRef object

-(void)addWeakRef:(id)ref


  DelegateRef *delRef = [DelegateRef new];

  [delRef setDelegateWeakReference:ref] 

  [array addObject:delRef];


第3步:稍后,如果属性delegateWeakReference == nil,则可以从数组中删除对象

该属性将为 nil,并且引用将在适当的时间释放,独立于该数组引用

【讨论】:

嗨 MistyD,请告诉我你为什么编辑这篇文章?我没有看到任何改变。请不要编辑我的帖子 您可以单击“已编辑”行以查看更改的内容。查看编辑,我看到 Misty 为您的代码块添加了格式,以便它们显示为代码,您的任何内容都没有更改。这是对某人进行的完全有效的编辑,您不应期望其他人不编辑您的帖子。 @HarishKumarKailas 他们正在将您的代码格式化为代码块,您已将其作为纯文本发布,这是不鼓励的。【参考方案11】:

如果你经常使用这个组件,它会显示给你自己的 NSMutableArray 类(NSMutableArray 的子类),它不会增加保留计数。

你应该有这样的东西:

-(void)addObject:(NSObject *)object 
    [self.collection addObject:[NSValue valueWithNonretainedObject:object]];


-(NSObject*) getObject:(NSUInteger)index 

    NSValue *value = [self.collection objectAtIndex:index];
    if (value.nonretainedObjectValue != nil) 
        return value.nonretainedObjectValue;
    

    //it's nice to clean the array if the referenced object was deallocated
    [self.collection removeObjectAtIndex:index];

    return nil;

【讨论】:

【参考方案12】:

我认为 Erik Ralston 先生在他的 Github 存储库中提出了一个优雅的解决方案

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

这是基本步骤:

为 NSArray 和 NSMutableArray 创建一个类别

在实现中创建一个具有弱属性的便利类。您的类别会将对象分配给此弱属性。

.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

【讨论】:

虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review 在答案中添加了代码,现在你能解释一下解决方案有什么问题才能被否决吗? 它可能由于缺少代码而被否决了,但不幸的是,无论谁对你投了反对票,你都懒得说什么(我也觉得这很烦人)。

以上是关于对 ARC 下对象的弱引用 (__unsafe_unretained) 的 NSArray的主要内容,如果未能解决你的问题,请参考以下文章

ARC机制之__strong具体解释

OC对象与CF对象的相互转换 和 ARC下查看OC对象的引用计数

总是将 self 的弱引用传递到 ARC 中的块中?

内存管理的思考方式2(ARC下)

__block 和__weak

容易导致循环引用的场景的解决方案