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

the*_*tic 249 iphone weak-references objective-c ios automatic-ref-counting

我对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];
}
Run Code Online (Sandbox Code Playgroud)

ProcessOperation.h

@interface ProcessOperation : NSOperation
{
    NSMutableArray *dataArr;
    NSMutableArray *rowHeightsArr;
    void (^callback)(NSMutableArray *rows);
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

jem*_*ons 707

这不仅有助于把重点放在strongweak讨论的一部分.而是专注于循环部分.

保留周期是当对象A保留对象B时发生的循环,对象B保留对象A.在这种情况下,如果释放了任一对象:

  • 对象A不会被释放,因为对象B保存对它的引用.
  • 但是只要对象A具有对它的引用,对象B就不会被释放.
  • 但是对象A永远不会被释放,因为对象B拥有对它的引用.
  • 无限的

因此,这两个对象只会在程序的生命周期中徘徊,即使它们应该,如果一切正常,也应该被释放.

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

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

该块保留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];     
}];
Run Code Online (Sandbox Code Playgroud)

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

因为,在这种情况下,self 根据定义已经有了strong对块的引用,通常最容易通过对要self使用的块进行明确的弱引用来解决:

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

但这不应该是你在处理调用块时遵循的默认模式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];
}];
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,这确实是一个完美的答案! (3认同)
  • 我不确定A保留B,B保留A会做一个无限循环.从引用计数的角度来看,A和B的引用计数是1.这种情况的保留周期是什么原因是当没有其他组具有A和B外部强引用时 - 这意味着我们无法达到这两个对象(我们不能控制A来释放B,反之亦然),因此,A和B相互引用以保持自己的生命. (2认同)

Leo*_*ica 26

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


Ilk*_*aci 24

我完全赞同@jemmons.

"但这不应该是你在处理调用self的块时所遵循的默认模式!这应该仅用于打破自我和块之间的保留周期.如果你在各处采用这种模式,你就是' d冒着将一个块传递给自我被解除分配后执行的东西的风险."

//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];
}];
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,我们可以在块内的weakSelf上定义一个强引用.

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  MyObject *strongSelf = weakSelf;
  [strongSelf doSomething];
}];
Run Code Online (Sandbox Code Playgroud)

  • 保留周期仅在它们存在于对象的静态状态时才有意义.当代码正在执行并且其状态不断变化时,多个且可能冗余的保留是可以的.无论如何,关于这个模式,捕获一个强引用,对于在块运行之前自我解除分配的情况没有做任何事情,这仍然可能发生.它确实在执行**块时确保self不会被释放**.如果块执行异步操作本身会给出一个窗口来实现这一点,这很重要. (12认同)
  • 这不是strongSelf的一个很好的例子,因为strongSelf的显式添加正是运行时所做的事情:在doSomething行上,在方法调用的持续时间内采用了强引用.如果weakSelf已经失效,则强ref为nil,方法调用为no-op.strongSelf帮助的地方是你有一系列操作,或访问一个成员字段(` - >`),你要保证你实际上有一个有效的引用并在整个操作集中持续保持它,例如`if( strongSelf){/*几个操作*/}` (5认同)
  • 不会自己增加weakSelf的引用计数吗?从而创建一个保留周期? (3认同)

Rob*_*Rob 19

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

你说:

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

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

有关更多信息,请参阅" 使用Objective-C:使用块编程"文档中的" 捕获自我时避免强引用循环"部分.

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

在Xcode 6中记录引用计数

或者在Xcode 5中:

在Xcode 5中记录引用计数