资讯专栏INFORMATION COLUMN

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

weizx / 2550人阅读

摘要:有没有什么更优雅,无感知的接入方式点此查看原文自修复技术实现与原理解析前言前言设计非常不合理,于是有很多的三方库,比如用更优的来规避这些,但是侵入性比较大,必须编码规范来约束所有人都要使用该方式。时及时移除时,让移除。

摘要: 【前言】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: ">: 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方法:

图片描述

(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 这样就完全不干扰我们平时的代码书写逻辑了。

扫码获取更多资讯:

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/16964.html

相关文章

  • iOS文章 - 收藏集 - 掘金

    摘要:本文将从设计模开发封装掘金前言很久以前,手机的性能取决于处理器的处理速度。对于要求苛刻的苹果竟然在如此简单的使用调试掘金一概述全称默认内置于中的动态调试工具。 iOS 开发 - 多线程陷阱 - iOS - 掘金前言 随着手机硬件的升级,多线程技术在应用开发中的地位可以说足以媲美UITableView了。然而,多线程技术在提供我们生产力的同时,也不可避免的带来了陷阱,正如著名计算机学者所...

    paraller 评论0 收藏0
  • iOS文章 - 收藏集 - 掘金

    摘要:本文将从设计模开发封装掘金前言很久以前,手机的性能取决于处理器的处理速度。对于要求苛刻的苹果竟然在如此简单的使用调试掘金一概述全称默认内置于中的动态调试工具。 iOS 开发 - 多线程陷阱 - iOS - 掘金前言 随着手机硬件的升级,多线程技术在应用开发中的地位可以说足以媲美UITableView了。然而,多线程技术在提供我们生产力的同时,也不可避免的带来了陷阱,正如著名计算机学者所...

    lei___ 评论0 收藏0
  • iOS文章

    摘要:在单核时代,使用多线程技术更多时候是为了避免耗时操作堵塞了主线程。而在多核时代,多线程技术才真正完成了提升执行效率的工作。 iOS 监控 - DNS 劫持 DNS 劫持指在劫持的网络范围内拦截域名解析的请求,分析请求的域名,把审查范围以外的请求放行,否则返回假的 IP 地址或者什么都不做使请求失去响应。 JavaScript深入系列15篇正式完结! 写在前面 JavaScript 深入...

    dreamans 评论0 收藏0
  • WKCrashSDK - crash拦截工具

    摘要:具备期发生的层级提示。这里划重点拦截时,存在部分三方库的不能拦截,以及系统的相机相册无需拦截,否则会出现无效的提示,在我的项目已经进行了白名单过滤。后续我会抽空将其加入豪华午餐注如从中直接拖入,则默认开启除了拦截外的其他种类型的拦截。 前言 由于线上始终出现部分未知原因崩溃问题,遂遵循网易出的crash拦截机制,自实现了一个crash拦截工具,现已上线运行数月,累计拦截闪退···总之很...

    silencezwm 评论0 收藏0
  • iOS 进阶必读 - 收藏集 - 掘金

    摘要:深入研究捕获外部变量和实现原理掘金前言是语言的扩充功能,而在和中引入了这个新功能。是由和两位大神在对的开发过程中中所有变换操作底层实现分析上掘金前言在上篇文章中,详细分析了是创建和订阅的详细过程。 深入研究Block捕获外部变量和__block实现原理 - 掘金 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这个新功能B...

    sf_wangchong 评论0 收藏0

发表评论

0条评论

weizx

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<