NSNotificationCenter removeObserver:在 dealloc 和线程安全中

Posted

技术标签:

【中文标题】NSNotificationCenter removeObserver:在 dealloc 和线程安全中【英文标题】:NSNotificationCenter removeObserver: in dealloc and thread-safety 【发布时间】:2012-12-04 08:08:54 【问题描述】:

我正在使用 ARC,我在观察者的 dealloc 中调用 [[NSNotificationCenter defaultCenter] removeObserver:someObserver];

来自NSNotificationCenter Class Reference

请务必在之前调用此方法(或 removeObserver:name:object:) notificationObserver 或指定的任何对象 addObserver:selector:name:object: 被释放。

NSNotificationCenter 不保留观察者。

Q1:NSNotificationCenter 线程安全吗?

如果观察者正在被释放(并从通知中心移除观察者)并且另一个线程同时发布通知。

我遇到随机崩溃,我怀疑是这种情况。

Q2:这种情况可能吗?

Q3:会不会导致EXC_BAD_ACCESS

Q4:那么,在观察者的dealloc 中调用[[NSNotificationCenter defaultCenter] removeObserver:someObserver]; 是否安全?

Q5:如果不安全,我应该在哪里拨打removeObserver:

【问题讨论】:

如果您使用 ARC,为什么要删除自己?编译器将释放它。 @AnoopVaidya 使用 ARC 会自动从 NSNotificationCenter 中移除观察者? 没有。即使使用 ARC,您也需要移除观察者。我一直都是在dealloc的方法里做的,不知道你的问题出在哪里…… 这里的问题不是要删除dealloc中的观察者,而是让dealloc被另一个线程而不是主线程调用。如果您的应用程序设计良好,则应在主线程中调用 dealloc(对于与 UI 相关的任何内容),并且删除观察者应该没有问题。见***.com/questions/2746197/… 可能对你有用lapcatsoftware.com/articles/… 【参考方案1】:

我自己只是偶然发现了这个问题:我有一个通知刚刚在发送过程中(这总是发生在主线程中),而对象正在从后台线程中释放。我通过在主线程中简单地执行removeObserver 并等待来修复它:

- (void)removeNotificationCenterObserver

    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self];


- (void)dealloc

    [self performSelectorOnMainThread:@selector(removeNotificationCenterObserver) withObject:self waitUntilDone:YES];

这会等到当前运行循环周期结束并在下一个运行循环周期开始时执行此消息。这可确保任何仍在运行的函数都能完成。

【讨论】:

这是唯一真正解决问题而不是叹息的答案。我希望我有更多的拇指给予。 谢谢。我也花了一段时间才弄清楚。并发编程很难。【参考方案2】:

我想知道同样的事情,但我找不到它的文档。这就是我认为正在发生的事情。

removeObserver: 并非您希望的那样是线程安全的。

考虑以下情况。在线程 A 上执行代码时释放对观察者的最后一个引用。线程 A 将调用观察者的 dealloc 方法。同时,被观察对象从线程 B 执行[NSNotificcationCenter postNotificationName:object:]。这导致了不可避免的竞争条件。也就是说,当您的对象在其dealloc 方法中时,通知将进行中

- (void)init 
    ...
    [[NSNotificcationCenter defaultCenter] addObserver:self
                                              selector:@selector(callback:)
                                                  name:@"whatever"
                                                object:nil];
    ...


- (void)dealloc 

    // If the observed object posts the notification on thread B while 
    // thread A is here, there's a race! At best, thread B will be
    // in callback: while thread A is here in dealloc. That's probably
    // not what you expect. The worst case is that thread B won't make
    // make it to callback: until after thread A completes the dealloc
    // and the memory has been freed. Likely crash!

    [[NSNotificationCenter defaultCenter] removeObserver:self];

    // If the observed object does the post when thread A is here,
    // you'll be fine since the observation has been removed.

这对于只观察其他主线程对象的主线程对象来说不是问题,因为根据定义,您无法进入我描述的线程 A 和 B 场景。

对于多线程情况,保证您避免该问题的唯一方法是确保观察停止观察者的引用计数达到 0。如果其他人负责观察者的生命周期(即你有任何termclose 方法),这很容易。如果没有,我不知道解决方案。

【讨论】:

【参考方案3】:

是的,NSNotificationCenter 不保留观察者,但它的调度表中仍有指向它的指针。

Q1:引用 Apple 文档

定期通知中心在发布通知的线程上发送通知。分布式通知中心在主线程上传递通知。有时,您可能需要在由您而不是通知中心确定的特定线程上传递通知。例如,如果在后台线程中运行的对象正在侦听来自用户界面的通知,例如窗口关闭,您希望在后台线程而不是主线程中接收通知。在这些情况下,您必须捕获在默认线程上传递的通知并将它们重定向到适当的线程。

Q2,3:是的。

Q4,5:AFAIK 它是安全的,除非你偶然发现循环引用。 我通常在 -viewWillAppear:/-viewWillDisappear: 中添加/删除 UIViewControllers 和 -init/dealloc 中的其他类。

【讨论】:

以上是关于NSNotificationCenter removeObserver:在 dealloc 和线程安全中的主要内容,如果未能解决你的问题,请参考以下文章

WiFi网络更改是不是有NSNotificationCenter通知?

NSNotificationCenter传值

消息通信机制NSNotificationCenter -备

NSNotificationCenter 内存泄漏

如何在 iOS 8 中使用带有 NSNotificationCenter 的小部件

关闭时未收到 NSNotificationCenter 通知