[译文]如何才能不崩溃 #3: NSNotification

原文地址:

http://inessential.com/2015/05/21/how_not_to_crash_3_nsnotification

实际上,相比起KVO我更喜欢NSNotification(特别是)在绑定上。我有的时候会使用KVO - 当有些时候,这是最明智的。但是NSNotification,像很多更老的API一样,在不引起崩溃的情况下,更容易使用。

但是你依然需要很小心。

崩溃的一种方法

当一个对象注册了一个通知,然后对象在释放的时候没有注销这个通知,然后在发送通知的时候,app就会崩溃。

大规则

我有一个简单的、硬性并快速的规则:通知只在主线程发送。没有例外。如果某些代码在另外一个线程上跑,并且它需要去发送一个通知,那它只能在主线程上这么做。

这能避免通知进入一个你不期望的线程所导致的所有问题。在注销通知后,这可以避免资源竞争。

一个app几乎所有的代码都应该运行在主线程上。运行在通知或者GCD队列上的代码应该从其他代码上独立出来,并且当多个对象一起工作的时候,应该使用代理模式(使用或不使用block)。

确保通知应该一直很容易的在主线程上发送。(我将在另一篇 如何才能不崩溃 - 线程、队列 的文章上进行详细描述。)

注销总括

很多人喜欢在dealloc中对每个通知做一个额外的注销工作。类似于这样:

[[NSNotificationCenter defaultCenter] removeObserver:self name:kSomeNotificationName object:someObject];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kSomeOtherNotificationName object:someOtherObject];
etc...

你可以证明当你写这代码的时候你是正确的。但是并没有对代码的整体进行足够考虑 - 你不得不去考虑你的代码,因为它会随着时间进行变更。

未来你或者某个人也许会添加另外一个通知,但是忘记调用removeObserver方法。然后导致了崩溃。

另一个问题是,也许以后的开发者需要检查你的代码,确保每个注册的观察者都被移除掉。这很痛苦:这太手动并且发送错误的可能性很大。

请总是使用这种方式去替代:

[[NSNotificationCenter defaultCenter] removeObserver:self];

这是印第安纳琼斯做的事情。

注意双重注册

如果一个对象注册了一个通知,然后又注册了一次,那么通知处理方法将会被调用两次。它不会自动合并。

(这曾经发生在以前很多iOS的viewDidLoad 中。人们会把注册代码放在那 - 但是记住,视图(views)可能会被卸载或者重新加载,这意味着会注册多个相同的通知。)

你的通知处理方法应该可以处理被调用两次的情况。或者,不应该让一个对象注册两个相同的通知。或者两者全部。

init中注册,在dealloc中注销

在几乎所有的单一案例中,我在init方法中注册观察者,在dealloc 中移除观察者。如果我发现,一个对象在生命周期中需要添加或者移除观察者,我会考虑这闻上去像一个健壮的代码。

这是一个好机会:

  • 要么这个对象并不需要这么做
  • 要么这个对象应该被拆解成更小的对象

你知道,对于一个对象来说,它的init方法应该只被调用一次。你知道,一个对象的dealloc方法也应该只被调用一次,在它没有其他引用的时候。你可以使用这个知识去平衡注册和注销,而不需要一直考虑或者追踪它。就这么简单。

避免使用addObserverForName

有些人会这么写:-[NSNotificationCenter addObserverForName: object: queue: usingBlock:]。这感觉很现代,应该它是基于block的,并且我们都爱block。(我确定。)

但这是一个很坏的想法。你可能已经写了一个通知的处理方法,但是这让你的管理更为混乱,因为现在你有一个额外的对象去保持,并且在以后要调用removeObserver:。这违反了上面的注销总括,这意味者你又回到了需要检查代码的事情上,这也意味这你要做其他的事情去确保它的正确。

你也许喜欢基于block的版本,意味着注册和注销的方法可以一起处理 - 但是在管理和潜在的崩溃上,这样的花费实在是太高了。

Article by 付军