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,同时也可以写,
同时也可以多次 addObserverremoveObserver 这样就完全不干扰我们平时的代码书写逻辑了。

扫码获取更多资讯:




本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/522696.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据中心网络架构的问题与演进 — 传统路由交换技术与三层网络架构

戳蓝字“CSDN云计算”关注我们哦&#xff01;文章目录目录传统路由交换技术路由和交换交换技术传统的 2 层交换技术具有路由功能的 3 层交换技术具有网络服务功能的 7 层交换技术路由技术三层网络架构核心层&#xff08;Core Layer&#xff09;汇聚层&#xff08;Aggregation L…

10分钟上线 - 利用函数计算构建微信小程序的Server端

摘要&#xff1a; 阿里云函数计算是一个事件驱动的全托管计算服务。通过函数计算&#xff0c;您无需管理服务器等基础设施&#xff0c;只需编写代码并上传。微信小程序是一种不需要下载安装即可使用的应用&#xff0c;它可以在微信内被便捷地获取和传播。 当微信小程序遇见serv…

'cross-env' 不是内部或外部命令,也不是可运行的程序

解决方案&#xff1a; 运行: cnpm i cross-env --save-dev

监控linux内存,linux 监控系统资源-内存

监控内存使用量&#xff1a;思路:使用free -m 提取相关数据&#xff0c;算出使用内存量输入到文件&#xff0c;并且每一次计算都与当前文件中的数据对比&#xff0c;大于文件中的数据则替换。保留当天最大内存使用量&#xff0c;每天发送邮件(可以单独写个发邮件的脚本)。脚本可…

AI+DevOps正当时

戳蓝字“CSDN云计算”关注我们哦&#xff01;随着业务复杂化和人员的增加&#xff0c;开发人员和运维人员逐渐演化成两个独立的部门&#xff0c;他们工作地点分离&#xff0c;工具链不同&#xff0c;业务目标也有差异&#xff0c;这使得他们之间出现一条鸿沟。而发布软件就是将…

SpringBoot集成Flowable_Jsite待办任务菜单报500

JSite 快速开发框架&#xff0c;内置Flowable工作流引擎 五大基础模块 前后端基础代码自动生成 权限精确控制。 说明&#xff1a;此版本我已经调通&#xff0c;最新版本正在更新&#xff0c;页面未处理好&#xff0c;因此采用历史版本。 文章目录一、克隆/打开项目1.1. 搜索…

阿里云SDK再升级,宣布支持C++语言

摘要&#xff1a; 日前&#xff0c;阿里云官方SDK发布支持新语言——C 语言SDK&#xff0c;意味着90%以上产品可以随时生成并发布C SDK&#xff0c;给C 语言的开发者使用。 此次阿里云发布支持C SDK的新功能&#xff0c;可以让C 语言开发者更加便捷地使用SDK调用产品API来操作产…

网络存储 linux 访问,Linux基础教程学习笔记28——使用Samba访问网络存储

Linux基础教程学习笔记28——使用Samba访问网络存储SMB用于Windows和类Linux系统直接的文件共享安装samba client包&#xff1a;[rootlinuxidc~]# yum install samba-client\* -y使用smbclinet命令查看和访问windows共享的文件夹资源&#xff1a;12345678910111213 [rootlinuxi…

IDC Q1中国云服务报告:公有云IaaS市场增速持续高于全球

2019年8月2日&#xff0c;市场研究机构IDC发布了《2019Q1中国公有云服务市场跟踪报告》。报告显示&#xff0c;中国公有云市场发展强劲&#xff0c;2019年Q1公有云IaaS市场同比增长74%&#xff0c;头部效应明显&#xff0c;市场集中度较去年持续提升。头部厂商中&#xff0c;阿…

SpringBoot集成Flowable_Jsite已办任务菜单报500

JSite 快速开发框架&#xff0c;内置Flowable工作流引擎 五大基础模块 前后端基础代码自动生成 权限精确控制。 说明&#xff1a;此版本我已经调通&#xff0c;最新版本正在更新&#xff0c;页面未处理好&#xff0c;因此采用历史版本。 文章目录一、克隆/打开项目1.1. 搜索…

《阿里巴巴Java开发手册》发布一周年!你不知道的背后故事!

摘要&#xff1a; 今天是2月9日&#xff0c;也是《阿里巴巴Java开发手册》&#xff08;下称《手册》&#xff09;对外正式发布一周年的日子。在过去的300多个日子里&#xff0c;这本小小的手册在业界产生了巨大的影响力。值此一周年之际&#xff0c;我们不妨一道围炉煮酒&#…

谁不喜欢《长安十二时辰》? | Alfred数据室

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | AlfredWu来源 | Alfred数据室最近&#xff0c;悄悄上线的《长安十二时辰》在朋友圈被吹爆了&#xff1a;年度最佳古装剧、服道化精致、电影质感、良心剧等赞美的声音不绝于耳。该剧首播时在豆瓣拿到了8.8的高分&#xff0c;但…

SpringBoot集成Flowable_Jsite已发任务菜单报500

JSite 快速开发框架&#xff0c;内置Flowable工作流引擎 五大基础模块 前后端基础代码自动生成 权限精确控制。 说明&#xff1a;此版本我已经调通&#xff0c;最新版本正在更新&#xff0c;页面未处理好&#xff0c;因此采用历史版本。 文章目录一、部门经理流转1. 登录dep…

linux按日期备份mysql,在Linux、Windows上如何按日期逻辑备份数据库

在逻辑备份数据库时&#xff0c;用户可能会希望在dmp文件中加入日期变量&#xff0c;以区分不同日期的备份文件&#xff0c;并且可以防止意外的覆盖。参考了eagle在逻辑备份数据库时&#xff0c;用户可能会希望在dmp文件中加入日期变量&#xff0c;以区分不同日期的备份文件&am…

一张图看懂阿里企业级分布式应用服务EDAS

摘要&#xff1a; 近日&#xff0c;阿里中间件&#xff08;Aliware&#xff09;的企业级分布式应用服务EDAS宣布再次升级&#xff0c;全面支持Spring Cloud应用。今后&#xff0c;使用Spring Cloud开源框架的应用可以实现0代码修改&#xff0c;即能在EDAS上平滑运行。目的是帮助…

SpringBoot集成Flowable_Jsite办理任务菜单报403

JSite 快速开发框架&#xff0c;内置Flowable工作流引擎 五大基础模块 前后端基础代码自动生成 权限精确控制。 说明&#xff1a;此版本我已经调通&#xff0c;最新版本正在更新&#xff0c;页面未处理好&#xff0c;因此采用历史版本。 文章目录一、克隆/打开项目1.1. 搜索…

漫画:有趣的“帽子问题”

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 小灰来源 | 程序员小灰————— 第二天 —————主持人让三名参与者各自戴上眼罩&#xff08;看不见外界&#xff09;&#xff0c;然后随机给每个参与者戴上一顶帽子。比如下面这样&#xff1a;然后&#xff0c;主持人…

【程序员归家计划】放假回家之前拜服务器?不存在的,这才是保证程序员过好年的正确打开方式

摘要&#xff1a; 在即将到来的214情人节和举国同庆的农历春节期间&#xff0c;运维同学们应该如何才能不被公司召回&#xff0c;如何才能保证系统的正常运转&#xff1f;本文就为大家整理了自动化运维、架构升级以及安全保障的相关干货合集&#xff0c;希望能够帮助各位运维同…

秘籍分享:如何将负载均衡按量付费实例转换为包年包月实例

摘要&#xff1a; hi&#xff0c;大家好&#xff0c;今天我来教大家如何将负载均衡从按量计费实例转换成预付费&#xff08;即包年包月&#xff09;实例。 点此查看原文&#xff1a;http://click.aliyun.com/m/42583/ hi&#xff0c;大家好&#xff0c;今天我来教大家如何将负…

Flowable快速工作流脚手架_Jsite角色授权不显示

JSite 快速开发框架&#xff0c;内置Flowable工作流引擎 五大基础模块 前后端基础代码自动生成 权限精确控制。 说明&#xff1a;此版本我已经调通&#xff0c;最新版本正在更新&#xff0c;页面未处理好&#xff0c;因此采用历史版本。 文章目录一、克隆/打开项目1.1. 搜索…