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


252

我对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);
}

流程操作

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

如果你对这个话题的深度话语想要一个读dhoerl.wordpress.com/2013/04/23/...
David H制作

Answers:


721

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

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

  • 对象A不会被释放,因为对象B拥有对它的引用。
  • 但是,只要对象A引用了对象B,就永远不会释放它。
  • 但是对象A永远不会被释放,因为对象B拥有对它的引用。
  • 广告无限

因此,即使在一切正常的情况下,这两个对象也应该在程序的生命周期内徘徊,即使它们在一切正常的情况下也应该被释放。

因此,我们担心的是保留周期,而创建这些周期的块本身没有任何问题。这不是问题,例如:

[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和block之间的保留循环。如果要在所有地方都采用这种模式,则冒着将块传递给在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];
}];

2
我不确定A保留B,B保留A会无限循环。从参考计数的角度来看,A和B的参考计数为1。导致这种情况的保留周期的原因是,当没有其他组在外部具有A和B的强参考时–这意味着我们无法达到这两个对象(我们不能控制A释放B,反之亦然),因此,A和B互相引用以保持自身的生命。
刘丹云

@Danyun的确,在释放对这些对象的所有其他引用之前,在A和B之间的保留周期是不可恢复的,但这并没有减少它的周期。相反,仅因为特定循环可能是可恢复的,并不意味着可以将其包含在代码中。保持周期散发出不良设计的味道。
jemmons

@jemmons是的,我们应该始终尽可能避免保留循环设计。
刘丹云

1
@大师我不可能说。这完全取决于您-setCompleteionBlockWithSuccess:failure:方法的实现。但是,如果paginator由拥有ViewController,并且ViewController在释放这些块之后不调用它们,则使用__weak引用将是安全的举动(因为self拥有拥有块的东西,因此当块调用它时,它仍然可能存在即使他们没有保留它)。但这就是很多“如果”。这实际上取决于应该执行的操作。
jemmons,2014年

1
@Jai不,这是块/关闭的内存管理问题的核心。当没有对象时,对象将被释放。MyObject并且SomeOtherObject双方拥有该块。但是由于该块的引用MyObjectweak,所以该块拥有MyObject。因此,尽管该块被保证,只要存在两种 MyObject SomeOtherObject存在,有没有保证,MyObject只要块确实会存在。MyObject可以完全取消分配,并且只要SomeOtherObject仍然存在,该块仍将在那里。
jemmons,2015年

26

您不必总是使用弱引用。如果您的块没有保留,而是被执行然后丢弃,则可以强烈地捕获自身,因为它不会创建保留周期。在某些情况下,您甚至希望该块保留自身,直到该块完成为止,这样它才不会过早地解除分配。但是,如果您强烈捕获该块,并在捕获自身内部,则会创建一个保留周期。


好吧,我只是将代码块作为回调执行,而且我根本不希望对self进行释放。但是似乎我正在创建保留周期,因为相关的视图控制器没有被取消分配...
the_critic

1
例如,如果您有一个由视图控制器保留的条形按钮项,并且在该块中强烈捕获自身,则会有一个保留周期。
Leo Natan

1
@MartinE。您应该只使用代码示例更新您的问题。Leo非常正确(+1),它不一定会导致较强的参考周期,但可能取决于您使用这些块的方式。如果您提供代码段,我们将更容易为您提供帮助。
罗布

@LeoNatan我了解保留周期的概念,但是我不确定在块中会发生什么,所以这让我有些困惑
the_critic

在上面的代码中,一旦操作完成并且块被释放,您的self实例将被释放。您应该阅读有关块如何工作以及它们在其范围内捕获的内容和时间的信息。
Leo Natan

26

我完全同意@jemmons:

但是,这不应是您在处理称为self的块时遵循的默认模式!这仅应用于打破在self和block之间的保留循环。如果要在所有地方都采用这种模式,则冒着将一个块传递给在释放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];
}];

为了克服这个问题,可以weakSelf在块内部定义一个强引用:

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

3
strongSelf不会增加weakSelf的引用计数吗?从而形成保留周期?
mskw

12
保留周期仅在对象处于静态时才重要。当代码正在执行且其状态不断变化时,可以进行多个(可能是多余的)保留。无论如何,对于这种模式,获取强大的引用对于在块运行之前自我被释放的情况没有任何作用,这仍然可能发生。它确实确保执行块时不会释放self 。如果该块本身进行异步操作,则为发生这种情况提供了一个窗口,这一点很重要。
皮埃尔·休斯顿,

@smallduck,您的解释很棒。现在,我对此有了更好的了解。书籍未涵盖此内容,谢谢。
张慧彬2015年

5
这不是StrongSelf的好例子,因为无论如何,显式添加的StrongSelf正是运行时所要做的:在doSomething行中,在方法调用期间使用了一个强引用。如果weakSelf已经失效,则强引用为nil,方法调用为no-op。如果您要进行一系列操作或访问一个成员字段(->),strongSelf会为您提供帮助,在此您要保证您确实获得了有效的引用并在整个操作集中连续保存它,例如if ( strongSelf ) { /* several operations */ }
Ethan

19

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

你说:

我理解保留周期的概念,但是我不确定在块中会发生什么,所以这让我有些困惑

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

有关更多信息,请参见“ 使用Objective-C编程:使用块”文档中的“ 避免在捕获自身时捕获强参考循环”部分。

如果您的视图控制器仍未释放,则只需确定未解析的强引用所在的位置(假设您确认已将其NSOperation释放)。一个常见的例子是重复使用NSTimer。或一些delegate错误地维护strong引用的自定义对象或其他对象。您通常可以使用Instruments来跟踪对象在何处获得其强引用,例如:

在Xcode 6中记录引用计数

或在Xcode 5中:

在Xcode 5中记录引用计数


1
另一个示例是,如果操作保留在块创建器中,并且一旦完成就不释放。+1就不错了!
Leo Natan

@LeoNatan同意,尽管代码片段将其表示为本地变量,如果他使用ARC,它将被释放。但是你说的没错!
罗布

1
是的,我只是举一个例子,正如OP在另一个答案中所要求的。
Leo Natan

顺便说一句,Xcode 8具有“ Debug Memory Graph”(调试内存图),这是查找对尚未发布的对象的强大引用的更简便方法。参见stackoverflow.com/questions/30992338/…
罗布

0

某些解释忽略了有关保留周期的条件[如果一组对象通过一组牢固的关系连接,即使没有来自该组的强大引用,它们也会彼此保持活动状态。]有关更多信息,请阅读文档。


-2

这是您可以在块内使用self的方式:

//调用块

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

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


};
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.