在ARC中总是将自我的弱引用传递给块?

时间:2013-11-17 12:51:57

标签: ios iphone objective-c automatic-ref-counting weak-references

我对Objective-C中的块使用有点困惑。我目前使用ARC,我的应用程序中有很多块,目前总是指self而不是弱引用。这可能是这些块保留self并防止其被解除分配的原因吗?问题是,我是否应始终在块中使用weak self的引用?

-(void)handleNewerData:(NSArray *)arr
{
    ProcessOperation *operation =
    [[ProcessOperation alloc] initWithDataToProcess:arr
                                         completion:^(NSMutableArray *rows) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateFeed:arr rows:rows];
        });
    }];
    [dataProcessQueue addOperation:operation];
}

ProcessOperation.h

@interface ProcessOperation : NSOperation
{
    NSMutableArray *dataArr;
    NSMutableArray *rowHeightsArr;
    void (^callback)(NSMutableArray *rows);
}

ProcessOperation.m

-(id)initWithDataToProcess:(NSArray *)data completion:(void (^)(NSMutableArray *rows))cb{

    if(self =[super init]){
        dataArr = [NSMutableArray arrayWithArray:data];
        rowHeightsArr = [NSMutableArray new];
        callback = cb;
    }
    return self;
}

- (void)main {
    @autoreleasepool {
        ...
        callback(rowHeightsArr);
    }
}

6 个答案:

答案 0 :(得分:686)

有助于不关注讨论的strongweak部分。而是专注于循环部分。

保留循环是当对象A保留对象B,对象B保留对象A时发生的循环。在这种情况下,如果任一对象被释放:< / p>

  • 对象A不会被释放,因为对象B拥有对它的引用。
  • 但只要对象A具有对它的引用,对象B就不会被释放。
  • 但是对象A永远不会被释放,因为对象B拥有对它的引用。
  • ad infinitum

因此,这两个对象只会在程序的生命周期中闲逛,即使它们应该在一切正常工作的情况下被释放。

所以,我们担心的是保留周期,并且没有关于创建这些周期的块本身。这不是问题,例如:

[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
   [self doSomethingWithObject:obj];
}];

该块保留self,但self不会保留该块。如果释放了一个或另一个,则不会创建任何循环,并且所有内容都会按原样释放。

你遇到麻烦的地方是:

//In the interface:
@property (strong) void(^myBlock)(id obj, NSUInteger idx, BOOL *stop);

//In the implementation:
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [self doSomethingWithObj:obj];     
}];

现在,您的对象(self)对该块有明确的strong引用。该块具有对self隐式强引用。这是一个循环,现在这两个对象都不会被正确释放。

因为,在这种情况下,self 按照定义已经对该块有strong引用,通常最简单的方法是通过明确弱引用来解决要使用的块self

__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [weakSelf doSomethingWithObj:obj];     
}];

但在处理调用self的块时,这不应该是您遵循的默认模式!这应该仅用于打破自我和块之间的保留周期。如果你在任何地方采用这种模式,你就有可能将块传递给在self被释放后执行的东西。

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not retained!
  [weakSelf doSomething];
}];

答案 1 :(得分:25)

您不必总是使用弱引用。如果您的块未被保留,但已执行然后被丢弃,则您可以强烈捕获自身,因为它不会创建保留周期。在某些情况下,您甚至希望块保持自身直到块完成,因此它不会过早释放。但是,如果您强烈捕获块,并且在捕获自身内部,则会创建一个保留周期。

答案 2 :(得分:23)

我完全赞同@jemmons。

&#34;但这不应该是你在处理调用self的块时遵循的默认模式!这应该仅用于打破自我和块之间的保留周期。如果你在任何地方采用这种模式,那么你就有可能将一个块传递给在自我解除分配后执行的东西。&#34;

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not  retained!
  [weakSelf doSomething];
}];

要解决这个问题,可以在块内的weakSelf上定义一个强引用。

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
   MyObject *strongSelf = weakSelf;
  [strongSelf doSomething];
}];

答案 3 :(得分:19)

正如Leo指出的那样,您添加到问题中的代码不会建议强大的参考周期(a.k.a。,保留周期)。可能导致强引用周期的一个与操作相关的问题是,如果操作未被释放。虽然你的代码片段表明你没有将你的操作定义为并发,但如果你有,那么如果你从未发布isFinished,或者如果你有循环依赖,或类似的东西,它将不会被释放。如果未释放操作,则视图控制器也不会被释放。我建议在您的操作NSLog方法中添加一个断点或dealloc,并确认它已被调用。

你说:

  

我理解保留周期的概念,但我不太清楚块中发生了什么,所以让我有点困惑

块发生的保留周期(强参考周期)问题就像您熟悉的保留周期问题一样。块将保持对块中出现的任何对象的强引用,并且在块本身释放之前不会释放那些强引用。因此,如果块引用self,或者甚至只引用self的实例变量,它将保持对self的强引用,那么在块被释放之前不会被解析(或者在这种情况下,直到NSOperation子类已发布。

有关详细信息,请参阅使用Objective-C编程:使用块文档的Avoid Strong Reference Cycles when Capturing self部分。

如果您的视图控制器仍未发布,则只需确定未解析的强引用所在的位置(假设您确认NSOperation已取消分配)。一个常见的例子是使用重复的NSTimer。或者某些自定义delegate或其他错误维护strong引用的对象。您经常可以使用Instruments来追踪对象获得强引用的位置,例如:

record reference counts in Xcode 6

或者在Xcode 5中:

record reference counts in Xcode 5

答案 4 :(得分:0)

一些解释忽略了关于保留周期的条件[如果一组对象通过一系列强关系连接,即使没有来自组外的强引用,它们也会保持活着。]有关更多信息,请阅读document

答案 5 :(得分:-2)

这是你在块内使用self的方法:

//调用块

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);
NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
{
             [obj MethodNameYouWantToCall]; // this is how it will call the object 
            return @"Called";


};