使用NSNotificationCenter添加/移除观察者的更好方法是什么?

28

我曾在viewDidLoad:中使用addObserver,并在dealloc:中使用removeObserver。 代码如下:

我曾经在viewDidLoad:里添加观察者,然后在dealloc:里移除观察者。代码如下:

- (void)viewDidLoad
{
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(refreshData)
                                                 name:AnyNotification
                                               object:nil];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:AnyNotification
                                                  object:nil];
}

但是根据一些文章所说,最好在viewDidAppear:中添加观察者并在viewDidDisappear:中移除观察者。 代码:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(refreshData)
                                                 name:AnyNotification
                                               object:nil];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:AnyNotification
                                                  object:nil];
}

那么,添加观察者/移除观察者的更好方法是什么呢?


在可能发生内存警告的情况下,在 dealloc 中删除观察者并不好。 - Eimantas
1
在视图消失时,确保为每个特定的通知移除观察者,而不是移除observer:self,否则当视图重新出现时,您将删除超类的通知观察者。 - chourobin
5个回答

27

这取决于您的情况,通常最好的方法是在 viewDidLoad 中添加,然后在 dealloc 以及 viewDidUnload(在 iOS 9.0 中已被弃用,请仅使用 dealloc)中删除。但是,在某些情况下,如果您有相同的方法存在于不同的类中(例如 UI 效果),并且希望仅调用当前屏幕的方法,则需要使用通知,在 viewWillAppear 中添加观察者,然后在 viewWillDisappearviewDidAppear/viewDidDisappear 中删除。

编辑: 来自评论的说明,感谢 @honey。

虽然现在在 iOS 9 及以上版本中再也不需要担心移除观察者了。请参见 苹果发布说明:“在 OS X 10.11 和 iOS 9.0 中,NSNotificationCenter 和 NSDistributedNotificationCenter 不再向已可能被解除分配的注册观察者发送通知。”


7
我认为第一个解决方案不是最好的,因为存在这样的情况:当类被初始化但视图未加载时,应用程序将崩溃并显示错误消息“Cannot remove an observer <CustomViewController 0xbbc19c0> for the key path "KEY_PATH" from <OBJ 0x111bc890> because it is not registered as an observer."。 - DanSkeel
2
@DanSkeel 或许可以添加 initawakeFromNib - Puttin
2
如果您需要在根视图控制器或任何当前视图控制器之前的视图控制器中更新某些日期,则如果使用viewWillAppear/Disappear,您的通知将不会触发到先前的视图控制器。正如问题中所提到的,这取决于具体情况。 - Saad
4
如果您有这样一种情况,那么很可能意味着 viewDidLoad 不是您应该编写观察者(observer)的地方。每个业务/UI需求都应该有自己适当的周期。不过现在自 iOS 9以来,你不再需要担心移除观察者了。请参见苹果发布说明:"在 OS X 10.11 和 iOS 9.0 中,NSNotificationCenter 和 NSDistributedNotificationCenter 不再向可能已被释放的已注册观察者发送通知..."。 - mfaani
@Saad - 谢谢,这是一个有用的知识点。我没有考虑到在考虑视图和前一个视图时事件发生的顺序。 - ToolmakerSteve
显示剩余2条评论

4
通常,我会将其放在-viewDidAppear:-viewDidDisappear:(或-viewWillAppear:-viewWillDisappear:)中,因为在我遇到的每个情况下,我只对视图实际显示时的通知感兴趣。
这可能是一种过早的优化(处理通知的代码可能需要一些时间,但如果未显示视图,则可能无用),但这也不是更多的代码 - 它只是在不同的方法中使用相同的代码...

1
这是正确的方法,因为视图有时可能会被加载但不会立即显示在屏幕上,例如在实现UIStateRestoring时。而且你可以利用viewWillAppear中的isMovingToParentViewController来确保只执行一次操作。 - malhal

1
不要忘记NSKeyValueObservingOptionInitial。我将其与viewWillAppear/viewWillDisappear一起使用,这样我的UI始终是最新的,即使我隐藏了该视图控制器,也可以节省资源,因为在再次显示之前不会更新它。

0

-viewWillAppear: + -viewWillDisappear:-viewDidAppear: + -viewDidDisapear: 更好,因为它们总是被调用相同的次数。


@ToolmakerSteve,它们是成对的。 - wzso
@ToolmakerSteve,当导航控制器的interactivePopGestureRecognizer启用时。 - wzso
好的,我现在明白你的意思了。我修改了你的措辞以使其更清晰,并为你点赞,因为我认为你提出了一个重要观点。(尽管我目前没有开发应用程序,所以我无法确认“Did”函数是否总是平衡的。)(可能是其他人将你的回答投了反对票,可能是因为和我一样被困惑了,无法理解你的意思。) - ToolmakerSteve
@ToolmakerSteve,(o^^o)我知道。但是由于我的英语写作水平不佳,无法准确表达。 - wzso

0

使用 NSNotifications 的最佳方法是在需要观察通知时添加观察者,并在不再需要时移除观察者

这可以在 viewDidLoad:viewWillAppear: 或用户点击某个按钮等情况下进行。

我给你举个小例子:

我的应用有一个选项卡,在一些视图控制器中,我会显示从互联网上下载的一些信息(例如一条推文)。我还有一个类负责每隔2分钟从服务器拉取新数据,并且当服务器有新数据时,我会更新数据库中的信息。我不会使用委托模式来监听数据库变化,因为我有很多视图控制器显示数据,将委托作为数组并循环传递数据给每个视图控制器将是非常糟糕的设计。所以,在这种特定的情况下,最好的做法是发布一个通知告诉每个视图控制器有新数据到来。

如果你的视图控制器在视图消失时移除委托,那么只有当前的视图控制器会收到通知并更新显示内容。

当然,你也可以在显示之前更新其他视图控制器的内容,例如在 viewWillAppear: 中进行,但这样做会导致视图控制器的内容不仅在必要时更新,而且每次切换选项卡时都会更新。

这只是一个例子,我试图向您展示当我们没有完整的应用程序行为描述时,很难建议您何时添加或删除观察者,特别是对于 NSNotifications。


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