如何不Crash之3: NSNotification(译)

原文地址
App crash是让大家很头疼的一件事情, 有些crash比较明显, 比如数组越界, 未实现的selector等等, 有些就比较隐蔽, 作者在写了一系列的文章, 描述了潜在的crash问题, 我针对自己的一些经验, 和遇到的问题, 会挑选一些翻译一下, 同时也结合自己的实例说一下看法. 有兴趣的可以看一下这个系列, 我相信会有一定帮助的.

如何不crash之3:通知

通常来说, 我更倾向于通知而不是KVO或者是绑定(译注1). 当KVO是最佳方案时, 我也会偶尔用KVO. 然而NSNotification, 与其它的陈年API一样, 简单易用且不容易crash. 但是你仍然要保持警惕.
(议: 最近刚好在KVO和通知之间进行了一番纠结, 现在网络上有一大堆的关于KVO和通知优劣的对比, 我只讲下自己的理解吧, KVO对keyPath依赖太重了, 监听一定要绑定到keypath, 而且KVO回调所携带的信息无法定制, 需要自己管理observer, 在属性可能发生变化的情况下, 比如, 你用了一个NSMutableDictionary来存储一些信息, 用KVO来监听这些信息的变化, 会显得非常棘手, 没有特殊情况, 还是推荐大家用NSNotification)

crash之路

当一个对象注册监听一个通知, 在dealloc的时候没有注销, 一旦这个通知被发出App就会crash. 这件事一定避免发生, 这篇文章接下来的部分会描述如何做到.(译注: 其实下面会讲到的不止这些, 不然我也不会专门翻译一下:) )

大的原则

我有一个简单,强硬且快速的原则: 通知只能在主线程发出(post), 没有例外. 如果代码运行在其它线程且需要发出一个通知, 那它需要在主线程发出这个通知.
这避免了所有通知到达你不期望的的线程造成的所有问题. 它避免了注销通知的竞态条件问题(译注2).
大部分的app代码都应该被执行到主线程. 用NSOperation或者GCD队列执行的代码应该与其它东西完全隔离, 当多个对象协作时, 应该用一个delegate模式或block(来传递数据).
总是在主线程发出通知也很容易. (我会写另外一篇关于线程和队列如何不crash的文章来阐述更多细节).

整体注销

有的人喜欢在dealloc中做一些额外的工作来为每一个通知注销, 就像下面一样:
[[NSNotificationCenter defaultCenter] removeObserver:self name:kSomeNotificationName object:someObject];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kSomeOtherNotificationName object:someOtherObject];
等等...
你可以证明你写的这些代码都是对的, 但是需要再考虑一下, 你必须思考你的代码随着时间推移会变成什么样.
在以后, 你或者别人可能会增加另外一个通知, 但是忘记了调用removeObserver来为他的通知注销, 然后就可能发生crash. 另一个问题在于未来的开发者可能来审查你的代码, 还要确保以下每一个注册的通知都得到了移除. 这太痛苦了: 这完全是人工活, 并且很容易出错. 所以, 应该总是用下面的代码来取代上面:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Indiana Jones 就是这么干的.

小心重复注册

如果一个对象注册了一个通知, 然后又注册了一次, 那么通知回调会被调用两次,这里并不会合并.(这种情况在过去iOS的viewDidLoad中经常发生. 开发者是应该把注册代码写在这里, 但是要记住, view可以被unload和reloaded, 这就意味着可能会出现对同样通知的多次注册).
(议: 总之, 弄明白哪些函数是可重入的, 哪些函数是不可重入的很重要, 如果是可重入的, 就一定要考虑重入的情况, 所以, 多测试内存警告的情况, 特别是接受通知的页面不在显示区域内的触发内存警告的情况)
你的通知处理代码应该要可以处理被重复调用的情况, 并且应该避免对给定对象重复注册同一个通知.

init时注册, dealloc时注销

在几乎所有的场景中, 我都在init方法中注册以及在dealloc中注销. 如果我发现一个对象添加和移除observation贯穿了整个对象的生命周期, 那我会考虑代码是否有强烈代码异味(strong code smell, 译注:所谓代码异味是指, 代码能够顺利完成其功能, 但是在逻辑和设计上却有很大的瑕疵, 比如很长的方法, 写重复的代码等等)
这是一个很好的机会(来审查): 要么它并不是真的需要这么干, 要么这个对象需要被拆分为更小的几个对象.
(议: 多多少少在开发阶段会遇到这样的问题, 要么是因为偷懒, 因为init不可重入, 直接在这里注册不需要考虑重复注册的问题, 要么是因为viewDidLoad的时机太晚, 需要更早的注册, 以免错过收到通知的正确时机. 其实目前我写的一块代码就有这样的情况, 用户自动登录, 一个view要监听登录成功的通知, 但是如果是在viewDidLoad里面写, 通知早就已经发出去了, 来不及接收, 仔细想想, 其实会有更好的解决办法的)
你知道对于一个给定的对象, init方法只会被调用一次, dealloc在没有其它引用的情况下也只会被调用一次. 你能利用这一点来保持注册和注销的平衡, 而不必
去思考或者追踪它, 多简单啊.

避免使用addObserverForName

有的人喜欢用-[NSNotificationCenter addObserverForName:​object:​queue:​usingBlock:]
它看起来更加现代, 因为是基于block的, 我们都爱block.(我也是)
然而, 这不是个好主意, 你可能想把自己从编写通知处理方法中解脱, 但是你却做了一个更坏的打算, 因为现在你需要额外的工作来执行removeObserver:.因为这意味着没有整体注销, 你又回到了代码审查, 必须把另外一件事情也做对. 你喜欢把注册和通知处理的代码写在一起而喜欢这种基于block的方法版本, 但是在收拾结局和潜在crash上却要付出很大的代价.

译注

译注1: 这个绑定应该是OSX下面的东西, 作者在这个系列文章的第一部分有描述
译注2: Race Condition, 竞态条件: 多线程中比较难处理的一类问题, 百度百科了解更多

一些心得

原作者的一系列文章都还很不错的, 我挑一些这一篇是因为自己比较有感触, 遇到了很多类似的问题, 不代表别的就不好哟. 推荐大家都去看看, 在平时开发的时候多注意一些, 会减少很多潜在的问题.

再说一句

陆陆续续收到一些朋友的关注和喜欢, 心里比较开心也比较担心, 毕竟我从事从学到实际开发iOS才1年多, 以前在学校是专门做C和Linux方面的, 所以很多东西如果理解的不透彻, 就不敢拿出来给大家看, 这也暴露了自己还没有完整的知识体系, 希望和大家一起成长.
前段之间一直在忙开发, 以后有机会会更多的写一些文章发在这里, 可能这种形式的译文+自己的议论会的偏多, 毕竟纯正自己的东西还是要不断积累的. 最近也在看runtime的源码, 和<Inside C++ Object Model>这本书. 有机会的话和大家一起分享.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容