NSNotificationCenter在dealloc中使用removeObserver:是否线程安全?

6

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

引自NSNotificationCenter类参考

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

NSNotificationCenter不会保留观察者。

Q1: NSNotificationCenter是否线程安全?

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

我遇到了随机崩溃,我怀疑这是问题所在。

Q2: 这种情况可能发生吗?

Q3: 是否会导致EXC_BAD_ACCESS

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

Q5: 如果不安全,我应该在哪里调用removeObserver:


如果您正在使用ARC,为什么要将自己移除?编译器会释放它。 - Anoop Vaidya
@AnoopVaidya 使用ARC会自动从NSNotificationCenter中删除观察者吗? - teerapap
3
不行。即使使用ARC,你仍需要移除观察者。我通常会在dealloc方法中执行此操作,我不知道你的问题出在哪里... - Guillaume
这里的问题不在于dealloc中移除观察者,而是dealloc被另一个线程调用而不是主线程。如果您的应用程序设计良好,则dealloc应该在主线程中调用(与UI相关的任何内容),并且在那里删除观察者不应该成为问题。请参见https://dev59.com/A03Sa4cB1Zd3GeqPv4U3#24410372。 - Gabriele Mondada
这篇文章可能对你有用:http://lapcatsoftware.com/articles/nsnotificationcenter-is-threadsafe-not.html - Maxim Kholyavkin
3个回答

14

我自己也遇到了这个问题:我有一个通知正在被发送(这总是发生在主线程中),而对象正在从后台线程中被释放。我通过在主线程中执行removeObserver并等待来解决了这个问题:

- (void)removeNotificationCenterObserver
{
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self];
}

- (void)dealloc
{
    [self performSelectorOnMainThread:@selector(removeNotificationCenterObserver) withObject:self waitUntilDone:YES];
}

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


这是唯一一个真正解决问题而不是对其叹息的答案。我希望我有更多的大拇指可以给予。 - CBGraham
谢谢。我也花了一段时间才弄明白。并发编程真的很难。 - felinira

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方法),那么这很容易解决。如果没有,我不知道有什么解决办法。


2
是的,NSNotificationCenter不会保留观察者,但它仍然在其派发表中具有指向观察者的指针。
Q1:引用苹果文档
常规通知中心在发布通知的线程上传递通知。分布式通知中心在主线程上传递通知。有时,您可能需要将通知传递到由您确定的特定线程,而不是通知中心。例如,如果在后台线程中运行的对象正在侦听来自用户界面(如窗口关闭)的通知,则希望在后台线程中接收通知,而不是在主线程中接收。在这些情况下,必须在默认线程上捕获通知,并将其重定向到适当的线程。
Q2,3:是的。
Q4,5:据我所知,这是安全的,除非遇到循环引用。对于UIViewControllers,我通常在-viewWillAppear:/-viewWillDisappear:中添加/删除,在其他类中则在-init/dealloc中添加/删除。

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