iOS KVO crash 自修復技術實現與原理解析

摘要: KVO API設計非常不合理,於是有很多的KVO三方庫,比如 KVOController 用更優的API來規避這些crash,但是侵入性比較大,必須編碼規範來約束所有人都要使用該方式。有沒有什麼更優雅,無感知的接入方式?

KVO crash 自修復技術實現與原理解析

前言

【前言】KVO API設計非常不合理,於是有很多的KVO三方庫,比如 KVOController 用更優的API來規避這些crash,但是侵入性比較大,必須編碼規範來約束所有人都要使用該方式。有沒有什麼更優雅,無感知的接入方式?

簡介

KVO crash 也是非常常見的 Crash 類型,在探討 KVO crash 原因前,我們先來看一下傳統的KVO寫發:

看到如上的寫發,大概我們就明白了 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。
  • 避免重複添加,避免重複移除。

報錯信息一覽:

防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方法:

之後我們就可以模擬dealloc中不寫removeObserver,同時也可以寫,

同時也可以多次 addObserverremoveObserver 這樣就完全不干擾我們平時的代碼書寫邏輯了。

更多技術乾貨敬請關注云棲社區知乎機構號:阿里云云棲社區 - 知乎


推薦閱讀:

從實際體驗來看,M7 處理器是否為 iPhone 5s 的續航帶來了顯著提升?
小碼哥教育培訓怎麼樣?
在Mac上做開發是否一定要用Retina屏?
最終幻想8 Final Fantasy Ⅷ為什麼沒有移植到iOS上?
為什麼蘋果不採用託管語言?

TAG:iOS | ObjectiveC | 編碼 |