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


70

我需要在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)的人,请改用以下问题:在ARC下对弱引用进行归零的集合


13
“我可怜那个弱小的foo!”
Emile Cormier 2012年

4
我建议不要打架,将NSPointerArray与NSPointerFunctionsWeakMemory NSPointerFunctionOption一起使用
leviathan

1
@leviathan:这个问题是在iOS 6发布之前问的。
Emile Cormier

我创建了是一个字典,用于存储对象作为有效归零弱引用。可以对其进行修改(并清理)以达到您的目的。
伊桑·里索

Answers:


75

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

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

另一种选择:可以选择性地存储弱引用的类别NSMutableArray

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


我的荣幸!另外,请查看我更新后的答案以获取其他选项。
yuji 2012年

我认为类别分类NSMutableArray是个坏主意,因为它是一个类集群,您会遇到很多麻烦。创建自己的NSObject具有与相同的所有方法的子类要好得多NSMutableArray
Abhi Beckert

1
您是否看过我链接到的实际解决方案?我不是NSMutableArray类集群的专家,但似乎正在建立使用特定后备存储的其他工厂方法,而不会影响其他任何类方法。
yuji 2012年

16
注意:这些不是自动归零的弱引用。他们只是不被保留。
mxcl 2012年

是的,正如@mxcl所说,这些都是不安全的,未保留的,不是弱小的。因此,如果您在取消分配值后尝试解开该值,它将崩溃。请改用NSMapTable和NSHashTable。
Mikhail Larionov 2014年

59

对于使用ARC,使用NSValue助手或创建集合(数组,集合,字典)对象并禁用其保留/释放回调的解决方案都不是100%故障安全解决方案。

正如对这些建议的各种评论所指出的那样,此类对象引用将无法像真正的弱引用一样工作:

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

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

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

为了获得第二种行为,您必须声明自己的帮助器类。它只有一个较弱的属性可供您参考。然后,您将此助手对象添加到集合中。

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

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

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


[NSPointer weakObjectsPointerArray]在iOS 6上仍然不是ARC__weak引用。因此,这不是一个更好的解决方案:就像您批评的解决方案一样,它符合行为#1,但不符合行为#2。它既优雅又方便,但不是ARC__weak参考。
马特2012年

@matt您如何发现weakObjectsPointerArray不返回__weak refs?您是否在代码中对其进行了测试,或者是否引用了“ NSPointerArray类参考”文档中的“重要提示:NSPointerArray不支持自动引用计数(ARC)下的弱引用”?我想知道这是否不仅仅是10.8年前的剩余时间,而且是文档错误。
Thomas Tempelmann,2012年

1
好吧,我从没想过盒子里有一个巨大的警告会“只是剩菜剩饭”。这些引用显然是弱的,并且在释放对象时它们显然被设置为NULL,但是我不知道如何测试它们是否__weak完全处于ARC的意义上。我会问的。
马特2012年

2
如果当对象被解除分配时它们变为NULL,则它们符合我的行为#2,那么一切都很好。在ARC之前,弱引用只是一个指针(类型ID),一旦其引用对象被释放,该指针便会悬空。ARC的弱引用在变为NULL的方式上更为智能,因此使用起来更安全。因此,如果您看到的是这种情况,那么文档中的警告框确实是一个错误。我还发送了有关此问题的反馈,请他们仔细检查。不幸的是,他们(苹果公司)没有给您反馈:(
Thomas Tempelmann,2012年

2
顺便说一句,如果您查看NSHashTable标题,+ (NSHashTable *)weakObjectsHashTable;您会发现一条评论:// entries are not necessarily purged right away when the weak object is reclaimed
Krzysztof Przygoda

27

在编写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
  }
}

概念证明(鉴于汤米对向量重新分配的关注):

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)

14
是的,但这不是真正的目标C解决方案。您只是喜欢它,因为您是c ++专家。
阿兰·穆赫兰

10
亚兰,我是一个寻求使用最佳可用工具完成工作的人。Objective-C没有很好的数据处理能力,本质上是C,带有一些消息传递技巧和庞大的支持库。这是一种过时且过时的语言(因此,苹果试图用SWIFT代替它)。由于我们有机会使用xcode附带的出色的c ++编译器,为什么不使用它呢?
理查德·霍奇斯

2
这不是Objective-C的尽可能的Objective-C是不是C.它是本地编译器支持,具有更好的内存管理能力
安德烈FRATELLI

2
我将所有Objective-C对象都包装在轻量级的C ++包装器中。它提供了安全的代码,并完全消除了Apple最新的惯用语言-Swift。
理查德·霍奇斯

1
我觉得那仅证明是可能的安全性-假设最终的现实与相同realloc,调整大小并不一定意味着运动。我猜我们还需要在插入第一件东西之后并在插入最后一件东西之前检查.data()&myThings[0]更改吗?很抱歉,我没有这么大的建设性-我是在工作中输入此字的,并提出了使用时间的苗条理由。我是认为可能有问题的人,如果您确定没有问题,请不要浪费更多的时间。我可以在闲暇时证明自己是错误的(并承诺会尝试这样做)。
汤米

13

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

NSPointerFunctionsWeakMemory

使用适合ARC或GC的弱读写屏障。在上一个发行版中,使用NSPointerFunctionsWeakMemory对象引用将变为NULL。


如果您可以容忍类似集合的行为(而不是类似数组的行为),这是正确的答案
bcattle 2016年


4

要将弱自我引用添加到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,请让我知道您为什么编辑此帖子?我没有看到任何变化。请不要编辑我的帖子
Harish Kumar Kailas,2015年

您可以单击“已编辑”行以查看更改。查看所做的编辑,我发现Misty为您的代码块添加了格式设置,以便它们显示为代码,而您的内容均未更改。对于某人来说,这是一个完全有效的编辑,您不应指望其他人不要编辑您的帖子。
约翰·斯蒂芬,

@HarishKumarKailas他们正在将您的代码格式化为一个代码块,您已将其发布为纯文本格式,不建议使用。
艾伯特·伦肖

4

最简单的解决方案:

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

注意:这也适用于iOS4.x。


2
尚不清楚什么使这些物体抱有弱物体。
Graham Perks 2014年

无分配器。查看此方法的声明CFMutableArrayRef CFArrayCreateMutable ( CFAllocatorRef allocator, CFIndex capacity, const CFArrayCallBacks *callBacks );您可以在文档中阅读更多内容。
ArtFeel 2014年

2
问题要求__unsafe_unretained并正确供应__unsafe_unretained; 但是,如果您不遵循原始作者对该术语的误用,那将是很好的weak
汤米

3

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

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


如果我将__unsafe_unretained指针包装到Foo可以由保留的另一个对象内部,该NSArray怎么办?
Emile Cormier 2012年

@EmileCormier您可以这样做,但应将其设为弱属性,而不是__unsafe_unretained。这样可以防止保留周期,但是您很容易最终访问垃圾。
杰森·可可

我希望我可以使用较弱的属性,但是我不能忽略我的应用程序的iOS 4.x市场。我的背景是C / C ++,所以我习惯在糟糕的过去玩火shared_ptr。:-)
Emile Cormier 2012年

根据您的应用发布的时间,您实际上可能想忽略iOS 4!:)如果遵循与iOS 4相同的趋势,则应在启动后6个月内在大约90%的设备中使用iOS 5,并且该标志是在4月,因此支持iOS 4的额外工作可能无法支付花费的时间进行编码/调试。
Fernando Madruga'2

2
只是有一个主意...__unsafe_unretained可以使用一个宏,而不是使用,而是根据基本SDK版本扩展为__unsafe_unretained或的宏__weak。这样,我可以在iOS 5设备上的开发过程中检测到悬空指针问题。
Emile Cormier 2012年

2

我刚刚遇到了同样的问题,发现我的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 responsesToSelector:]:消息发送到已释放实例0x1f03c8c0

已检查iOS版本4.3、5.1、6.2。希望对别人有用。


1

如果需要将弱引用归零,请参见此答案以获取可用于包装器类的代码。

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


1

如果您经常使用此注释,则会将其指示给您自己的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;
}

-2

我认为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

虽然此链接可以回答问题,但最好在此处包括答案的基本部分,并提供链接以供参考。如果链接的页面发生更改,仅链接的答案可能会失效。-评分
佰仁

在答案中添加了代码,现在您可以解释该解决方案出了什么问题才能被否决吗?
Enrico Cupellini

它可能由于缺少代码而被否决了,但是不幸的是,无论谁被否决了,您都不会说什么(我也很讨厌)。
奈杰尔·
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.