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

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

ViewController(可能因为从导航堆栈中popup而被释放)代码:

__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从导航堆栈中popup,则应用程序崩溃。 我错过了什么?

当你使用__unsafe_unretained ,那么即使在对象被释放之后,引用依然存在。 所以如果视图控制器被popup,那么weakSelf现在指向一个解除分配的对象。

如果你把它改为__weak ,那么当视图控制器被取消分配时,它会将weakSelf设置nil ,你会没事的。 您甚至不需要检查weakSelf是否设置为任何值,因为在nil上调用方法没有任何影响。

似乎不less人认为,一个街区内的“自我”一定是一个虚弱的(或者不具备)的副本。 通常不是这种情况**。

在这种情况下,误解是通过留下一个僵尸造成的。 正确的做法是在块中直接引用self(而不是unsafe_unretained,而不是weak),就像你希望普通代码看起来一样。 其效果是块将保留“self”指向的实例 – 在这种情况下是视图控制器 – 并且在块被销毁之前(大概由url请求者)销毁它将不被销毁。

视图控制器是否会破坏Web请求的结果,即使它已被popup? 几乎肯定不是,但是如果你认为这样做的话,检查块中的条件。

 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)和一个泄漏。 这可以发生在任何对象的块引用,而不仅仅是“自我”。

但在你的情况下(和许多人一样),自引用的视图控制器并不保留这个块。

使用块(尤其是延迟块)的好的做法是在调用方法中创build块的本地副本。 在你的情况下,应该在 – (void)sendUrl:successBlock:

 successBlockCopy = [successBlock copy]; 

然后打电话

 successBlockCopy(nil); 

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

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