iOS KVO crash 自修复技术实现与原理解析
摘要: 【前言】KVO API設計非常不合理,于是有很多的KVO三方庫,比如 KVOController 用更優的API來規避這些crash,但是侵入性比較大,必須編碼規范來約束所有人都要使用該方式。有沒有什么更優雅,無感知的接入方式?
點此查看原文:http://click.aliyun.com/m/41952/
KVO crash 自修復技術實現與原理解析
前言
【前言】KVO API設計非常不合理,于是有很多的KVO三方庫,比如?KVOController?用更優的API來規避這些crash,但是侵入性比較大,必須編碼規范來約束所有人都要使用該方式。有沒有什么更優雅,無感知的接入方式?
簡介
KVO crash 也是非常常見的 Crash 類型,在探討 KVO crash 原因前,我們先來看一下傳統的KVO寫發:
//#define MyKVOContext(A) static void * const A = (void*)&A; static void * const MyContext = (void*)&MyContext;// KVO注冊監聽:// _A 監聽 _B 的 @"keyPath" 屬性//[self.B addObserver: self.A forKeyPath:@"keyPath" options:NSKeyValueObservingOptionNew context:MyContext];- (void)dealloc {// KVO反注冊[_B removeObserver:_A forKeyPath:@"keyPath"]; }// KVO監聽執行 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {if(context != MyContext) {[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];return;}if(context == MyContext) {//if ([keyPath isEqualToString:@"keyPath"]) {id newKey = change[NSKeyValueChangeNewKey];BOOL boolValue = [newKey boolValue];} }看到如上的寫發,大概我們就明白了 API 設計不合理的地方:
B 需要做的工作太多,B可能引起Crash的點也太多:
B 需要主動移除監聽者的時機,否則就crash:
- B 在釋放變為nil后,hook dealloc時機
- A 在釋放變為nil后 否則報錯?Objective-C Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
KVO的被觀察者dealloc時仍然注冊著KVO導致的crash
B 不能移除監聽者A的時機,否則就crash:
- B沒有被A監聽
- B已經移除A的監聽。
添加KVO重復添加觀察者或重復移除觀察者(KVO 注冊觀察者與移除觀察者不匹配)導致的crash。
采取的措施:
- B添加A監聽的時候,避免重復添加,移除的時候避免重復移除。
- B dealloc時及時移除 A
- A dealloc時,讓 B 移除A。
- 避免重復添加,避免重復移除。
報錯信息一覽:
2018-01-24 16:08:54.100667+0800 BootingProtection[63487:29487624] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<CYLObserverView: 0x7fb287002fb0; frame = (0 0; 207 368); layer = <CALayer: 0x604000039360>>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.防crash措施
于是有很多的KVO三方庫,比如?KVOController?用更優的API來規避這些crash,但是侵入性比較大,必須編碼規范來約束所有人都要使用該方式。有沒有什么更優雅,無感知的接入方式?
那便是我們下面要講的 KVO crash 防護機制。
我們可以對比下其他的一些KVO防護方案:
網絡上有一些類似的方案,“大白健康系統”方案大致如下:
KVO的被觀察者dealloc時仍然注冊著KVO導致的crash 的情況,可以將NSObject的dealloc swizzle, 在object dealloc的時候自動將其對應的kvodelegate所有和kvo相關的數據清空,然后將kvodelegate也置空。避免出現KVO的被觀察者dealloc時仍然注冊著KVO而產生的crash
這樣未免太過麻煩,我們可以借助第三方庫?CYLDeallocBlockExecutor?hook 任意一個對象的 dealloc 時機,然后在 dealloc 前進行我們需要進行的操作,因此也就不需要為 NSObject 加 flag 來進行全局的篩選。flag 效率非常底,影響 app 性能。
“大白健康系統”思路是建立一個delegate,觀察者和被觀察者通過delegate間接建立聯系,由于沒有demo源碼,這種方案比較繁瑣。可以考慮建立一個哈希表,用來保存觀察者、keyPath的信息,如果哈希表里已經有了相關的觀察者,keyPath信息,那么繼續添加觀察者的話,就不載進行添加,同樣移除觀察的時候,也現在哈希表中進行查找,如果存在觀察者,keypath信息,那么移除,如果沒有的話就不執行相關的移除操作。要實現這樣的思路就需要用到methodSwizzle來進行方法交換。我這通過寫了一個NSObject的cagegory來進行方法交換。示例代碼如下:
下面是核心的swizzle方法:
| addObserver:forKeyPath:options:context: | cyl_crashProtectaddObserver:forKeyPath:options:context: |
| removeObserver:forKeyPath: | ?cyl_crashProtectremoveObserver:forKeyPath: |
| removeObserver:forKeyPath:context: | cyl_crashProtectremoveObserver:forKeyPath:context: |
之后我們就可以模擬dealloc中不寫removeObserver,同時也可以寫,
同時也可以多次?addObserver、removeObserver?這樣就完全不干擾我們平時的代碼書寫邏輯了。
掃碼獲取更多資訊:
總結
以上是生活随笔為你收集整理的iOS KVO crash 自修复技术实现与原理解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【HBase从入门到精通系列】如何避免H
- 下一篇: 阿里云基于NVM的持久化高性能Redis