这是来源于inessential.com的一系列博客,我觉得内容写的非常好,也非常有意义,所以我在此进行一系列的翻译。下面是原博客地址:http://inessential.com/hownottocrash
原文地址:
http://ifujun.com/yi-wen-ru-he-cai-neng-bu-beng-kui-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的版本,意味着注册和注销的方法可以一起处理 - 但是在管理和潜在的崩溃上,这样的花费实在是太高了。