使用ARC和代码块方法时,NSNotificationCenter的视图控制器dealloc未被调用

8
当我在我的视图控制器的-viewDidLoad:方法中使用-addObserverForName: object: queue: usingBlock:来监听NSNotificationCenter时,-dealloc方法最终没有被调用。(当我移除-addObserverForName: object: queue: usingBlock:时,-dealloc会再次被调用。)
使用-addObserver: selector: name: object:似乎没有这个问题。我做错了什么?(我的项目使用ARC。)
以下是我的实现示例,以防我在这里做错了什么:
[[NSNotificationCenter defaultCenter] addObserverForName:@"Update result"
                                                  object:nil
                                                   queue:nil
                                              usingBlock:^(NSNotification *note) {
                                                  updateResult = YES;
                                              }];

感谢您的帮助。我尝试添加以下内容(但没有成功):
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    if ([self isMovingFromParentViewController]) {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
}
3个回答

17

updateResult 是一个实例变量,它被那个块所保留,使得该对象不会被释放。

换句话说,你遇到了一个保留循环。对象保留了块,而块也保留了对象。

你需要创建一个弱引用或者 unsafe_unretained 引用来解除它们之间的关系。

在你的通知块之前添加以下代码:

__unsafe_unretained YouObjectClass *weakSelf = self;

或者(如果您正在使用iOS5及以上版本)

__weak YouObjectClass *weakSelf = self;

然后,在该代码块中,通过这个新的弱引用引用对象:

[[NSNotificationCenter defaultCenter] addObserverForName:@"Update result"
                                                  object:nil
                                                   queue:nil
                                              usingBlock:^(NSNotification *note) {
                                                  weakSelf.updateResult = YES;
                                              }];
请注意,保留循环引用本身并不是一件坏事。有时您实际上希望它们发生。但这些情况下,您必须确信在特定时间之后循环会被打破(例如动画块)。一旦块已执行并从堆栈中移除,循环就会被打破。

是的,这也会创建一个保留循环。有时这样的循环完全没有问题 - 但仅限于块被保证执行的情况下。一旦块完成执行(例如动画块),保留循环将被打破。 - Till
这个答案对我很有帮助。我注意到在不使用ARC时没有这样的问题。我想知道为什么?对象仍然必须保留self,而self也必须保留对象。 - IPhone Guy
@IPhoneGuy:遇到了同样的问题。苹果的文档“过渡到ARC发布说明”中有以下引用:“在手动引用计数模式下,__block id x; 的效果是不保留x。在ARC模式下,__block id x; 默认保留x(就像所有其他值一样)。也许这可以帮助解决问题? - Geoff Hom
这将允许 dealloc 被调用,因为观察者不再引用控制器。当控制器被释放时,观察者是否停止调用通知块?还是会调用但弱引用将为空,什么也不会发生?如果它仍在被调用,我们该如何删除该观察者? - hasan
好的,这个块会被调用,但由于弱引用为nil,什么也不会发生。我们仍然需要在dealloc中移除观察者,以停止对已释放控制器调用块。 - hasan
显示剩余2条评论

6

这很可能是由于您存在保留循环。

当您的块隐式地保留了self,并且self以某种方式保留了该块时,通常会出现这种情况。 每个对象都会保留另一个对象,它们的retainCount永远不会达到零,从而产生了保留循环。

您应该启用警告-Warc-retain-cycles,以便在出现此类问题时进行警告。

因此,在您的情况下,您使用变量updateResult,我假设它是实例变量,并且它隐式保留了self。 您应该使用一个临时的弱变量来表示self,并在块中使用它,以便它不被保留并且破坏保留循环。

__block __weak typeof(self) weakSelf = self; // weak reference to self, unretained by the block
[[NSNotificationCenter defaultCenter] addObserverForName:@"Update result"
                                              object:nil
                                               queue:nil
                                          usingBlock:^(NSNotification *note) {
                                              // Use weakSelf explicitly to avoid the implicit usage of self and thus the retain cycle
                                              weakSelf->updateResult = YES;
                                          }];

0

这不是保留循环。

NSNotificationCenter 持有 block,block 又持有 self。因为 [NSNotificationCenter defaultCenter] 是一个生命周期跨越整个应用的单例,所以间接地持有了 self


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接