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

时间:2012-02-17 22:23:44

标签: objective-c ios5 ios4 automatic-ref-counting

我需要在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

12 个答案:

答案 0 :(得分:73)

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

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

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

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

答案 1 :(得分:54)

使用 NSValue 帮助程序或创建集合(数组,集合,字典)对象和disable its Retain/Release callbacks的解决方案都不是关于使用ARC的100%故障安全解决方案。< / p>

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

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

  1. 对目标对象没有强烈的引用。这意味着如果对象没有指向它的强引用,则该对象将被释放。
  2. 如果ref'd对象被释放,则弱引用将变为nil。
  3. 现在,虽然上述解决方案符合行为#1,但它们没有展示#2。

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

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

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

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

答案 2 :(得分:24)

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

答案 3 :(得分:13)

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

一起使用
  

<强> NSPointerFunctionsWeakMemory

     

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

答案 4 :(得分:10)

我认为最好的解决方案是使用NSHashTable或NSMapTable。键或/和值可能很弱。您可以在此处详细了解:http://nshipster.com/nshashtable-and-nsmaptable/

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

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

答案 6 :(得分: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,并且引用将在适当的时间解除分配,与此数组引用无关

答案 7 :(得分:3)

不,那不对。那些实际上并不是弱参考。您现在无法在数组中存储弱引用。您需要有一个可变数组,并在完成它们时删除引用,或者在完成它时删除整个数组,或者滚动您自己的支持它的数据结构。

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

答案 8 :(得分:2)

我刚遇到同样的问题,发现我的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] addet to   设定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] addet to   设定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] addet to   设定2013-06-30 00:59:10.883 not_retained_collection_test [28997:907]    deallocated 2013-06-30 00:59:10.883   not_retained_collection_test [28997:907]   deallocated 2013-06-30 00:59:10.884   not_retained_collection_test [28997:907]   deallocated 2013-06-30 00:59:10.885   not_retained_collection_test [28997:907] * - [TestObj   respondsToSelector:]:发送到解除分配的实例0x1f03c8c0的消息

使用iOS版本4.3,5.1,6.2进行检查。 希望它对某人有用。

答案 9 :(得分:1)

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

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

答案 10 :(得分: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;
}

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