You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#warning move this to top of .m file
//#define MyKVOContext(A) static void * const A = (void*)&A;staticvoid * 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) {
[superobserveValueForKeyPath: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-2416: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
*** Terminating app due to uncaught exception'NSInternalInconsistencyException', reason: 'An instance 0x7f8827d21d20 of class XXXX was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x61000003db00> (<NSKeyValueObservance 0x61000025ae80: Observer: 0x7f882890b4c0, Key path: dataSource, Options: <New: YES, Old: NO, Prior: NO> Context: 0x10dfe7730, Property: 0x61000025b810>)'
*** First throw call stack:
(
0 CoreFoundation 0x00000001102b0b0b __exceptionPreprocess + 1711 libobjc.A.dylib 0x00000001167eb141 objc_exception_throw + 482 CoreFoundation 0x0000000110319625 +[NSExceptionraise:format:] + 1973 Foundation 0x0000000111322b53 NSKVODeallocate + 2944 UIKit 0x00000001138ec544 __destroy_helper_block_.125 + 805 libsystem_blocks.dylib 0x00000001185a999d _Block_release + 1116 UIKit 0x00000001139bd187 -[UIViewAnimationBlockDelegate .cxx_destruct] + 437 libobjc.A.dylib 0x00000001167e99bc _ZL27object_cxxDestructFromClassP11objc_objectP10objc_class + 1278 libobjc.A.dylib 0x00000001167f5d34objc_destructInstance + 1299 libobjc.A.dylib 0x00000001167f5d66object_dispose + 2210 libobjc.A.dylib 0x00000001167ffb8e _ZN11objc_object17sidetable_releaseEb + 20211 CoreFoundation 0x000000011021952d -[__NSDictionaryI dealloc] + 12512 libobjc.A.dylib 0x00000001167ffb8e _ZN11objc_object17sidetable_releaseEb + 20213 libobjc.A.dylib 0x00000001168002fa _ZN12_GLOBAL__N_119AutoreleasePoolPage3popEPv + 86614 CoreFoundation 0x00000001101ffe96 _CFAutoreleasePoolPop + 2215 CoreFoundation 0x000000011023baec __CFRunLoopRun + 217216 CoreFoundation 0x000000011023b016 CFRunLoopRunSpecific + 40617 GraphicsServices 0x0000000118f1ea24 GSEventRunModal + 6218 UIKit 0x0000000113904134 UIApplicationMain + 15919 HaiDiLao 0x000000010d50b5ef main + 11120 libdyld.dylib 0x000000011856265d start + 121 ??? 0x00000000000000010x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
前言
【前言】KVO API设计非常不合理,于是有很多的KVO三方库,比如 KVOController 用更优的API来规避这些crash,但是侵入性比较大,必须编码规范来约束所有人都要使用该方式。有没有什么更优雅,无感知的接入方式?
简介
KVO crash 也是非常常见的 Crash 类型,在探讨 KVO crash 原因前,我们先来看一下传统的KVO写发:
看到如上的写发,大概我们就明白了 API 设计不合理的地方:
B 需要做的工作太多,B可能引起Crash的点也太多:
B 需要主动移除监听者的时机,否则就crash:
Objective-C Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
KVO的被观察者dealloc时仍然注册着KVO导致的crash
B 不能移除监听者A的时机,否则就crash:
添加KVO重复添加观察者或重复移除观察者(KVO 注册观察者与移除观察者不匹配)导致的crash。
采取的措施:
报错信息一览:
其他情况的crash
防crash措施
于是有很多的KVO三方库,比如 KVOController 用更优的API来规避这些crash,但是侵入性比较大,必须编码规范来约束所有人都要使用该方式。有没有什么更优雅,无感知的接入方式?
那便是我们下面要讲的 KVO crash 防护机制。
我们可以对比下其他的一些KVO防护方案:
网络上有一些类似的方案,“大白健康系统”方案大致如下:
这样未免太过麻烦,我们可以借助第三方库 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
这样就完全不干扰我们平时的代码书写逻辑了。The text was updated successfully, but these errors were encountered: