僵尸在后台线程中调用完成块时

时间:2014-01-08 18:10:05

标签: ios automatic-ref-counting nszombie

我将完成块传递给我的方法,当网络请求完成时,将在后台调用此完成块。不幸的是,如果在此期间释放调用对象,应用程序崩溃:

ViewController(可能因为它从导航堆栈中弹出而被解除分配)代码:

__unsafe_unretained ViewController *weakSelf = self;

[[URLRequester instance] sendUrl:url successBlock:^(id JSON) {
    [weakSelf webserviceCallReturned:JSON];
}];

URLRequester-Code(当然更简单):

- (void)sendUrl:(NSString *)urlAfterHost successBlock:(void (^)(id))successBlock {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(2);
        successBlock(nil);
        return;
    });
}

如果在这2秒内,ViewController从导航堆栈中弹出,应用程序崩溃了。我错过了什么?

3 个答案:

答案 0 :(得分:2)

当您使用__unsafe_unretained时,即使在取消分配对象后,引用仍然存在。因此,如果视图控制器被弹出,那么weakSelf现在指向一个解除分配的对象。

如果您将其更改为__weak,那么当视图控制器被取消分配时,它会将weakSelf设置为nil,您就可以了。您甚至不需要检查weakSelf是否设置为任何内容,因为在nil上调用方法无效。

答案 1 :(得分:2)

似乎有不少人认为一个街区内的'自我'必须始终是一个弱(或未保留)的副本。通常情况并非如此**。

在这种情况下,误会造成了崩溃,留下了一个僵尸。正确的做法是在块中直接引用self(不是unsafe_unretained,而不是弱),就像你喜欢普通的代码一样。效果将是块将保留由'self'指向的实例 - 在这种情况下是视图控制器 - 并且在块被销毁之前它不会被销毁(可能是由url请求者)。

即使已经弹出了Web请求的结果,它是否会损坏视图控制器?几乎可以肯定没有,但是如果你认为的话,那就检查一下这个条件。

if (![self.navigationController.viewControllers containsObject:self])
    // I must have been popped, ignore the web request result

   // Re the discussion in comments, I think a good coder should have misgivings about
   // this condition.  If you think you need it, ask yourself "why did I design
   // my object so that it does something wrong based on whether some other object
   // (a navigation vc in this case) contains it?"

   // In that sense, the deliberate use of weakSelf is even worse, IMO, because
   // it lets the coder ignore _and_obscure_ an important question.
else {
    // do whatever i do when the web request completes
}

**块中弱指针或未指定指针的需要源于块将保留它们引用的对象的事实。如果其中一个对象直接或间接地保留了块,那么你得到一个循环(A保留B保留A)和泄漏。这可能发生在块引用的任何对象上,而不仅仅是“自我”。

但在你的情况下(如许多),self引用的视图控制器不会保留块。

答案 2 :(得分:0)

使用块(特别是延迟的)的好习惯是在调用方法中创建块的本地副本。在你的情况下,它应该在 - (void)sendUrl:successBlock:

中完成
successBlockCopy = [successBlock copy];

然后致电

successBlockCopy(nil);

它应该保留你的viewController一段时间直到完成。

最好使用__weak而不是__unsafe_unretained来避免突然释放的对象出现问题。