日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS KVO crash 自修复技术实现与原理解析

發布時間:2024/8/23 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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寫發:

#warning move this to top of .m file //#define MyKVOContext(A) static void * const A = (void*)&A; static void * const MyContext = (void*)&MyContext;#warning move this to viewdidload or init method // KVO注冊監聽:// _A 監聽 _B 的 @"keyPath" 屬性//[self.B addObserver: self.A forKeyPath:@"keyPath" options:NSKeyValueObservingOptionNew context:MyContext];- (void)dealloc {// KVO反注冊[_B removeObserver:_A forKeyPath:@"keyPath"]; }// KVO監聽執行 #warning — please move this method to the class of _A - (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方法:

原函數swizzle后的函數
addObserver:forKeyPath:options:context:cyl_crashProtectaddObserver:forKeyPath:options:context:
removeObserver:forKeyPath:?cyl_crashProtectremoveObserver:forKeyPath:
removeObserver:forKeyPath:context:cyl_crashProtectremoveObserver:forKeyPath:context:
- (void)cyl_crashProtectaddObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{if (!observer || !keyPath || keyPath.length == 0) {return;}@synchronized (self) {NSInteger kvoHash = [self _cyl_crashProtectHash:observer :keyPath];if (!self.KVOHashTable) {self.KVOHashTable = [NSHashTable hashTableWithOptions:NSPointerFunctionsStrongMemory];}if (![self.KVOHashTable containsObject:@(kvoHash)]) {[self.KVOHashTable addObject:@(kvoHash)];[self cyl_crashProtectaddObserver:observer forKeyPath:keyPath options:options context:context];[self cyl_willDeallocWithSelfCallback:^(__unsafe_unretained id observedOwner, NSUInteger identifier) {[observedOwner cyl_crashProtectremoveObserver:observer forKeyPath:keyPath context:context];}];__unsafe_unretained typeof(self) unsafeUnretainedSelf = self;[observer cyl_willDeallocWithSelfCallback:^(__unsafe_unretained id observerOwner, NSUInteger identifier) {[unsafeUnretainedSelf cyl_crashProtectremoveObserver:observerOwner forKeyPath:keyPath context:context];}];}}}- (void)cyl_crashProtectremoveObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context {//TODO: 加上 context 限制,防止父類、子類使用同一個keyPath。[self cyl_crashProtectremoveObserver:observer forKeyPath:keyPath];}- (void)cyl_crashProtectremoveObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{//TODO: white listif (!observer || !keyPath || keyPath.length == 0) {return;}@synchronized (self) {if (!observer) {return;}NSInteger kvoHash = [self _cyl_crashProtectHash:observer :keyPath];NSHashTable *hashTable = [self KVOHashTable];if (!hashTable) {return;}if ([hashTable containsObject:@(kvoHash)]) {[self cyl_crashProtectremoveObserver:observer forKeyPath:keyPath];[hashTable removeObject:@(kvoHash)];}}}

之后我們就可以模擬dealloc中不寫removeObserver,同時也可以寫,
同時也可以多次?addObserver、removeObserver?這樣就完全不干擾我們平時的代碼書寫邏輯了。

掃碼獲取更多資訊:




總結

以上是生活随笔為你收集整理的iOS KVO crash 自修复技术实现与原理解析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。