首页天道酬勤iosblock实现原理(crashlandsios)

iosblock实现原理(crashlandsios)

admin 12-03 02:48 315次浏览

00-1010【前言】KVO API的设计非常不合理,所以有很多KVO三方库,比如KVOController使用更好的API来避免这些崩溃,但是它的侵入性相当大,所以有必要通过代码规范来限制大家使用这个方法。有没有更优雅不易察觉的访问方法?

前言

KVO坠机也是非常常见的坠机类型。在讨论KVO崩盘的原因之前,我们先来看看传统的KVO写作:

#警告将此移动到顶部。m文件//#定义MyKVOContext(A)静态void * const A=(void *)A;静态void * const MyContext=(void *)MyContext;#警告将此移动到viewdidload或init方法

//KVO注册显示器:

//_A侦听_B的@'keyPath '属性。

//[自我。b附加服务器:自身。一个forKeyPath:@'keyPath '选项3360 skyvaleobservingoption new context : mycontext];-(无效)dealloc {//KVO取消注册

[_ B removeobserverr : _ A forkeypath : @ ' key path '];

}//KVO监视器执行#警告-请将此方法移动到对象3360 (ID)对象change:(NSDictionary *)更改context:(void *)上下文{ if(context!=MyContext){ 0

[super observevaluforkeypath : keypath of object : object change : change context : context];返回;

} if(context==MyContext){//if([KeyPath isequalttostring : @ ' KeyPath ']){ }

id new KeY=change[nskeyValueChangeNewKeY];BOOL boolValue=[new key boolValue];

}

}看了上面的编写和发送,大概明白了API设计的不合理之处:

B需要做的工作太多,B可能导致Crash的点太多:

b需要主动清除监听者的机会,否则死机:

释放后的挂钩交易机会为零

否则,在a被释放为nil后,将报告一个错误。objective-c thread 1: exc _ bad _ access(代码=exc _ i386 _ gpflt)

KVO观察到的dealloc仍然记录了KVO造成的崩溃。

b不能移除监听器A的机会,否则死机:

b不受a监控。

b去掉了a的听力。

因添加KVO反复添加观察员或反复删除观察员(KVO注册观察员与删除观察员不匹配)导致崩溃。

采取的措施:

向显示器添加A时,避免重复添加,移除时避免重复移除。

如果是dealloc,请及时移除

Dealloc,让B去掉a。

避免重复添加和移除。

错误消息列表:

2018-01-24 163:08:54.100667 0800启动保护[63487:29487624] ***由于未捕获的异常而终止应用程序' in internalinconsistencycexception ',原因n : ' cyloobserverview :0x 7 FB 287002 FB 0;帧=(0 0;207 368);层=CALLAYER 33600 x 604000393603360 an-对象:的ObserveValueForkeypath :更改3360上下文:消息已收到但未处理。防撞措施

所以有很多KVO三方库,比如KVOController,它用比较好的API来避免这些崩溃,但是它的侵入性比较大,所以需要代码规范来限制大家使用这个方法。有没有更优雅不易察觉的访问方法?

这就是我们将在下面讨论的kvoclash保护机制。

我们可以比较一些其他的KVO保护计划:

网上也有一些类似的方案。“大白健康体系”的方案大致如下:

当观察到的KVO dealloc仍然注册到由KVO引起的崩溃时,当对象dealloc注册到KVO时,NSObject的dealloc swizzle可以自动归其对应的kvodelegate所有。

数据清空,然后将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 list if (!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 这样就完全不干扰我们平时的代码书写逻辑了。

了解更多请微博关注阿里云客户满意中心

服务API版本控制设计与实践C++内存管理工具primitives怎么用详解c语言整形和浮点数在内存中的存储
假设检验和t检验的区别(如何用excel进行t检验) python垃圾语言(java 垃圾回收机制)
相关内容