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

摘要: 在开发中 unrecognized selector sent to instance XXXXX 是非常常见的 crash 类型。这篇博文主要介绍如何在客户端自修复该问题,并进行原理解析。

作者介绍:阿里云-移动云-大前端团队。

点此查看原文:http://click.aliyun.com/m/40470/

前言
在开发中 unrecognized selector sent to instance XXXXX 是非常常见的 crash 类型。

例如调用以下一段代码就会产生crash

[[NSNull null] performSelector:@selector(fooDoesNotRecognizeSelector1)];

具体 crash 时的表现见下:

2018-01-11 16:28:04.433573+0800 CYLSwizzleMainDemo[13252:156773356] -[NSNull fooDoesNotRecognizeSelector1]: unrecognized selector sent to instance 0x102870ef0
2018-01-11 16:28:04.440436+0800 CYLSwizzleMainDemo[13252:156773356] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull fooDoesNotRecognizeSelector1]: unrecognized selector sent to instance 0x102870ef0'
*** First throw call stack:
(0   CoreFoundation                      0x00000001025a712b __exceptionPreprocess + 1711   libobjc.A.dylib                     0x0000000101c3bf41 objc_exception_throw + 482   CoreFoundation                      0x0000000102628024 -[NSObject(NSObject) doesNotRecognizeSelector:] + 1323   CoreFoundation                      0x0000000102529f78 ___forwarding___ + 14324   CoreFoundation                      0x0000000102529958 _CF_forwarding_prep_0 + 1205   CYLSwizzleMainDemo                  0x0000000101321cef -[AppDelegate application:didFinishLaunchingWithOptions:] + 5276   UIKit                               0x0000000103315ac6 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 2997   UIKit                               0x0000000103317544 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 41138   UIKit                               0x000000010331c9e7 -[UIApplication _runWithMainScene:transitionContext:completion:] + 17209   UIKit                               0x00000001036e5fb0 __111-[__UICanvasLifecycleMonitor_Compatability _scheduleFirstCommitForScene:transition:firstActivation:completion:]_block_invoke + 92410  UIKit                               0x0000000103abb998 +[_UICanvas _enqueuePostSettingUpdateTransactionBlock:] + 15311  UIKit                               0x00000001036e5ba9 -[__UICanvasLifecycleMonitor_Compatability _scheduleFirstCommitForScene:transition:firstActivation:completion:] + 24912  UIKit                               0x00000001036e6423 -[__UICanvasLifecycleMonitor_Compatability activateEventsOnly:withContext:completion:] + 69613  UIKit                               0x0000000104063fe9 __82-[_UIApplicationCanvas _transitionLifecycleStateWithTransitionContext:completion:]_block_invoke + 26214  UIKit                               0x0000000104063ea2 -[_UIApplicationCanvas _transitionLifecycleStateWithTransitionContext:completion:] + 44415  UIKit                               0x0000000103d410a0 __125-[_UICanvasLifecycleSettingsDiffAction performActionsForCanvas:withUpdatedScene:settingsDiff:fromSettings:transitionContext:]_block_invoke + 22116  UIKit                               0x0000000103f40126 _performActionsWithDelayForTransitionContext + 10017  UIKit                               0x0000000103d40f63 -[_UICanvasLifecycleSettingsDiffAction performActionsForCanvas:withUpdatedScene:settingsDiff:fromSettings:transitionContext:] + 23118  UIKit                               0x0000000103abaff5 -[_UICanvas scene:didUpdateWithDiff:transitionContext:completion:] + 39219  UIKit                               0x000000010331b266 -[UIApplication workspace:didCreateScene:withTransitionContext:completion:] + 52320  UIKit                               0x00000001038f5b97 -[UIApplicationSceneClientAgent scene:didInitializeWithEvent:completion:] + 36921  FrontBoardServices                  0x0000000106d74cc0 -[FBSSceneImpl _didCreateWithTransitionContext:completion:] + 33822  FrontBoardServices                  0x0000000106d7d7b5 __56-[FBSWorkspace client:handleCreateScene:withCompletion:]_block_invoke_2 + 23523  libdispatch.dylib                   0x0000000105fd933d _dispatch_client_callout + 824  libdispatch.dylib                   0x0000000105fde9f3 _dispatch_block_invoke_direct + 59225  FrontBoardServices                  0x0000000106da9498 __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 2426  FrontBoardServices                  0x0000000106da914e -[FBSSerialQueue _performNext] + 46427  FrontBoardServices                  0x0000000106da96bd -[FBSSerialQueue _performNextFromRunLoopSource] + 4528  CoreFoundation                      0x000000010254a101 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 1729  CoreFoundation                      0x00000001025e9f71 __CFRunLoopDoSource0 + 8130  CoreFoundation                      0x000000010252ea19 __CFRunLoopDoSources0 + 18531  CoreFoundation                      0x000000010252dfff __CFRunLoopRun + 127932  CoreFoundation                      0x000000010252d889 CFRunLoopRunSpecific + 40933  GraphicsServices                    0x000000010763b9c6 GSEventRunModal + 6234  UIKit                               0x000000010331e4d2 UIApplicationMain + 15935  CYLSwizzleMainDemo                  0x00000001013230bf main + 11136  libdyld.dylib                       0x0000000106055d81 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) 

这类 crash 尤其在混合开发,或者 JS 与 native 交互中经常遇到,非常影响用户体验,也降低了 app 的质量与稳定性。

常见的 crash 场景可以总结为:

JSON 解析后,空值解析为 NSNULL 对象,造成 crash
JS 调用 native 方法,结果由于native底版本,或者 JS 代码编写的问题,找不到方法,导致 app crash。
在研究如何实现 app 自修复该 bug 前,我们可以研究下什么时候会报 unrecognized selector 的异常?

什么时候会报unrecognized selector的异常?

objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:

Method resolution

objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。

Fast forwarding

如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。
只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。
这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。

Normal forwarding

这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。

拦截调用的整个流程即 Objective-C 的消息转发机制。其具体流程如下图:

图片描述

unrecognized selector sent to instance XXXXX crash 自修复技术实现
原理简单来说:

当调用该对象上某个方法,而该对象上没有实现这个方法的时候,

可以通过“消息转发”进行解决。

可以利用消息转发机制的三个步骤,选择哪一步去改造比较合适呢?

这里我们选择了第二步forwardingTargetForSelector。引用 《大白健康系统–iOS APP运行时Crash自动修复系统》 的分析:

resolveInstanceMethod 需要在类的本身上动态添加它本身不存在的方法,这些方法对于该类本身来说冗余的
forwardInvocation 可以通过 NSInvocation 的形式将消息转发给多个对象,但是其开销较大,需要创建新的 NSInvocation 对象,并且 forwardInvocation 的函数经常被使用者调用,来做多层消息转发选择机制,不适合多次重写
forwardingTargetForSelector 可以将消息转发给一个对象,开销较小,并且被重写的概率较低,适合重写
选择了 forwardingTargetForSelector 之后,可以将 NSObject 的该方法重写,做以下几步的处理:

具体如下:

hook forwardingTargetForSelector 方法
添加白名单,限制hook的范围,排除内部类,并自定义需要hook的类
创建桩类 ForwardingTarget
为桩类动态添加对应的selector的imp,指向一个函数,返回 NSNull 对象
将消息转移到该桩类对象 ForwardingTarget 上
将 hook 掉的 crash 信息进行上报,方便发现问题,后期修复掉。
添加白名单,避免出现hook内部方法以及不必要的对象。
内部对象的特征是都以 _ 开头。
其他需要限制的部分,经常会出现在组件化开发、SDK开发中,避免影响到其他模块的正常工作,可以用类的前缀做区分。

其中动态创建的方法,返回值为什么返回一个 NSNull,而不是其他的值。

这样做的好处在于,在设置白名单的时候,只需要将 NSNull 设置进白名单,就可以解决方法返回值调用方法造成的crash。返回其他类型,就需要在白名单中多设置一种类型。

可以解决如下问题:

 id foo = [[NSNull null] performSelector:@selector(fooDoesNotRecognizeSelector1)];[foo performSelector:@selector(fooDoesNotRecognizeSelector2)];

hook时注意如果对象的类本事如果重写了forwardInvocation方法的话,就不应该对forwardingTargetForSelector进行重写了,否则会影响到该类型的对象原本的消息转发流程。

通过重写NSObject的forwardingTargetForSelector方法,我们就可以将无法识别的方法进行拦截并且将消息转发到安全的桩类对象中,从而可以使app继续正常运行。

具体的实现代码如下:

//
//  ForwardingTarge.h
//  
//
//  Created by ChenYilong on 18/01/10.
//  Copyright © 2018年  All rights reserved.
//#import <Foundation/Foundation.h>@interface ForwardingTarget : NSObject@end
//
//  ForwardingTarge.m
//  
//
//  Created by ChenYilong on 18/01/10.
//  Copyright © 2018年  All rights reserved.
//#import "ForwardingTarget.h"
#import <objc/runtime.h>@implementation ForwardingTargetid ForwardingTarget_dynamicMethod(id self, SEL _cmd) {return [NSNull null];
}+ (BOOL)resolveInstanceMethod:(SEL)sel {class_addMethod(self.class, sel, (IMP)ForwardingTarget_dynamicMethod, "@@:");[super resolveInstanceMethod:sel];return YES;
}- (id)forwardingTargetForSelector:(SEL)aSelector {id result = [super forwardingTargetForSelector:aSelector];return result;
}- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {id result = [super methodSignatureForSelector:aSelector];return result;
}- (void)forwardInvocation:(NSInvocation *)anInvocation {[super forwardInvocation:anInvocation];
}- (void)doesNotRecognizeSelector:(SEL)aSelector {[super doesNotRecognizeSelector:aSelector]; // crash
}@end
//
//  NSObject+DoesNotRecognizeSelectorExtension.h
//  
//
//  Created by ChenYilong on 18/01/10.
//  Copyright © 2018年  All rights reserved.
//#import <Foundation/Foundation.h>@interface NSObject (DoesNotRecognizeSelectorExtension)@end
//
//  NSObject+DoesNotRecognizeSelectorExtension.m
//  
//
//  Created by ChenYilong on 18/01/10.
//  Copyright © 2018年 All rights reserved.
//#import "NSObject+DoesNotRecognizeSelectorExtension.h"
#import <objc/runtime.h>
#import "ForwardingTarget.h"static ForwardingTarget *_target = nil;@implementation NSObject (DoesNotRecognizeSelectorExtension)+ (void)load {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{_target = [ForwardingTarget new];;not_recognize_selector_classMethodSwizzle([self class], @selector(forwardingTargetForSelector:), @selector(doesnot_recognize_selector_swizzleForwardingTargetForSelector:));});
}+ (BOOL)isWhiteListClass:(Class)class {NSString *classString = NSStringFromClass(class);BOOL isInternal = [classString hasPrefix:@"_"];if (isInternal) {return NO;}BOOL isNull =  [classString isEqualToString:NSStringFromClass([NSNull class])];BOOL isMyClass  = [classString ...];return isNull || isMyClass;
}- (id)doesnot_recognize_selector_swizzleForwardingTargetForSelector:(SEL)aSelector {id result = [self doesnot_recognize_selector_swizzleForwardingTargetForSelector:aSelector];if (result) {return result;}BOOL isWhiteListClass = [[self class] isWhiteListClass:[self class]];if (!isWhiteListClass) {return nil;}if (!result) {result = _target;}return result;
}#pragma mark - private methodBOOL not_recognize_selector_classMethodSwizzle(Class aClass, SEL originalSelector, SEL swizzleSelector) {Method originalMethod = class_getInstanceMethod(aClass, originalSelector);Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSelector);BOOL didAddMethod =class_addMethod(aClass,originalSelector,method_getImplementation(swizzleMethod),method_getTypeEncoding(swizzleMethod));if (didAddMethod) {class_replaceMethod(aClass,swizzleSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));} else {method_exchangeImplementations(originalMethod, swizzleMethod);}return YES;
}@end

参考文献:

《大白健康系统–iOS APP运行时Crash自动修复系统》
《iOSInterviewQuestions》

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

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

相关文章

阿里测试环境运维及研发效率提升之道

摘要&#xff1a; 生产环境最关注的就是稳定&#xff0c;测试环境更关注的是研发效率&#xff0c;如何从一行代码最快的保证质量发到线上去&#xff0c;这个是我们测试环境最关注的。在全球运维大会上&#xff0c;阿里巴巴研发效能事业部运维中台技术专家——刘湘疆&#xff08…

首批8款5G手机获3C认证:华为占4款;IBM获AT&T“几十亿美元”云计算合同;马库斯:未来薪酬将以Libra发放...

戳蓝字“CSDN云计算”关注我们哦&#xff01;嗨&#xff0c;大家好&#xff0c;重磅君带来的【云重磅】特别栏目&#xff0c;如期而至&#xff0c;每周五第一时间为大家带来重磅新闻。把握技术风向标&#xff0c;了解行业应用与实践&#xff0c;就交给我重磅君吧&#xff01;重…

第9篇:Flowable-Modeler集成以及集成代码下载

接上一篇&#xff1a; 第8篇&#xff1a;Flowable-Modeler集成之Flowable-modeler源码编译 https://blog.csdn.net/weixin_40816738/article/details/102901026 文章目录一、背景二、代码修改&#xff0c;去除认证2.1. 修改拦截请求2.2. 修改用户查询信息2.3. 账号查询请求修改…

如何保障研发质量不踩坑?阿里技术专家教你几招

摘要&#xff1a; 面对自动化测试成本高、测试不稳定、测试无法严控发布质量等常见研发过程中的测试问题时&#xff0c;企业如何避免&#xff1f;如何保障研发质量&#xff1f;阿里巴巴研发效能事业部-研发协同平台高级技术专家李帅&#xff08;花名焦霸&#xff09;&#xff0…

保障了罗振宇跨年演讲的PTS铂金版正式上线,产品体验全新升级

摘要&#xff1a; 虽然2018年的跨年已经过去&#xff0c;但是对于今年各种新颖的跨年形式&#xff0c;不少人仍然意犹未尽。比如&#xff0c;罗振宇在深圳卫视和优酷直播的跨年演讲《时间的朋友》。据悉&#xff0c;当天现场参与人数近万&#xff0c;观看直播的观众多达百万。而…

第10篇:Flowable-BPMN操作流程部署、启动

接上一篇&#xff1a; 第9篇&#xff1a;Flowable-Modeler集成以及集成代码下载 https://blog.csdn.net/weixin_40816738/article/details/102901208 文章目录一、背景二、方案设计2.1. 流程部署2.2. 模型的转换2.3. 启动流程三、BPMN业务流程文件3.1. 启动flowable-idm3.2. 启…

那些年我们用过神级的代码注释

戳蓝字“CSDN云计算”关注我们哦&#xff01;来自&#xff1a;Blankj | 责编&#xff1a;乐乐链接&#xff1a;http://github.com/Blankj/awesome-comment正文 写在前面的话&#xff1a;一时兴起就收集了以下神注释&#xff0c;希望能为广大ITer带来快乐&#xff0c;缓解你们工…

AliOS Things 组件系统(uCube)

摘要&#xff1a; AliOS Things 是阿里巴巴提供的物联网操作系统&#xff0c;可以在不同的设备上运行不同的功能&#xff0c;甚至相同的设备运行不同的功能&#xff0c;AliOS Things 基于组件管理&#xff1a; 1、 组件功能单一&#xff0c;复用组件提供的功能&#xff0c;比如…

第11篇:Flowable-BPMN部署常见问题没有对ACT_RE_PROCDEF表进行插入操作

上一篇&#xff1a; 第10篇&#xff1a;Flowable-BPMN操作流程部署、启动 https://blog.csdn.net/weixin_40816738/article/details/102902348 文章目录一、问题描述二、问题定位三、解决方案四、验证结果一、问题描述 流程在部署的时候调用服务RepositoryService&#xff0c;…

AliOS Things lorawanapp应用介绍

摘要&#xff1a; 文本旨介绍AliOS Things的lorawanapp的示例&#xff0c;完成一个LoRaWAN网络的构建和数据传输&#xff0c;并通过该示例让大家对AliOS Things有一个初步的了解。 点此查看原文&#xff1a;http://click.aliyun.com/m/40591/ AliOS Things 是 AliOS 家族旗下的…

漫画:什么是最小生成树?

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 小灰来源 | 程序员小灰————— 第二天 —————————————————首先看看第一个例子&#xff0c;有下面这样一个带权图&#xff1a;它的最小生成树是什么样子呢&#xff1f;下图绿色加粗的边可以把所有顶点连…

一种基于AliOS Things的uData感知设备软件框架

摘要&#xff1a;   uData框架设计之初的思想是基于传统sensorhub概念基础之上的&#xff0c;结合IoT的业务场景和AliOS Things物联网操作系统的特点设计而成的一个面对IoT的感知设备处理框架。 点此查看原文&#xff1a;http://click.aliyun.com/m/40592/ uData诞生背景uDat…

第12篇:Flowable-BPMN操作流程之用户任务UserTask

接上一篇&#xff1a; 第11篇&#xff1a;Flowable-BPMN部署常见问题没有对ACT_RE_PROCDEF表进行插入>操作 https://blog.csdn.net/weixin_40816738/article/details/102902524 文章目录一、定义二、常用配置三、参数赋值四、监听类实现五、BPMN配置六、验证6.1. 创建新流程…

通用智能传感集线器(Sensorhub)介绍

摘要&#xff1a;   智能传感集线器&#xff0c;也称之为Sensor hub&#xff0c;是一种基于低功耗MCU和轻量级RTOS操作系统之上的软硬件结合的解决方案&#xff0c;其主要功能是连接并处理来自各种传感器设备的数据。 点此查看原文&#xff1a;http://click.aliyun.com/m/405…

第13篇: Flowable-BPMN操作流程之流程进展查看之流程图

接上一篇&#xff1a; 第12篇&#xff1a;Flowable-BPMN操作流程之用户任务UserTask https://blog.csdn.net/weixin_40816738/article/details/102902596 文章目录一、背景二、原理三、实现方案3.1. 流程是否完成功能3.2. 完成流程图3.3. 控制器入口四、验证测试4.1. 创建流程4…

阿里云MaxCompute,用计算力让数据发声

摘要&#xff1a; 计算的价值绝不止计算本身&#xff0c;而是让本不会说话的数据发声。 从玛雅历法到圆周率&#xff0c;从万有引力定律到二进制&#xff0c;从固化的物体到虚拟的思维都由数据注入。阿里云大数据计算服务MaxCompute以技术驱动产品&#xff0c;用计算力让数据发…

命令行编译java项目_命令行编译运行java工程(转)

平时建立Java工程都是借助eclipse或intellij这些ide编辑器来构建&#xff0c;对于java工程的实际编译执行原理&#xff0c;从未了解过。作为一个曾经的C程序员&#xff0c;对于源码刨根问底的那份执着从未丢过。于是今天就写了这样的一个例子进行测试。1.首先建立个跟目录MyJav…

面试鹅厂,我三面被虐的体无完肤……

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | codegoose来源 | https://segmentfault.com/a/1190000017864721经过半年的沉淀&#xff0c;加上对MySQL&#xff0c;redis和分布式这块的补齐&#xff0c;终于重拾面试信心&#xff0c;再次出征。鹅厂面试职位&#xff1a;go…

阿里云MaxCompute印度开服,加速大数据产业升级

摘要&#xff1a; 2018年1月18日&#xff0c;阿里云大数据计算服务MaxCompute将在印度正式开服。通过MaxCompute强大的计算能力&#xff0c;阿里云将加速印度大数据产业的全面升级。 点此查看原文&#xff1a;http://click.aliyun.com/m/40728/ 2018年1月18日&#xff0c;阿里云…

第14篇:Flowable-BPMN操作流程之任务完成

接上一篇&#xff1a; 第13篇&#xff1a; Flowable-BPMN操作流程之流程进展查看之流程图 https://blog.csdn.net/weixin_40816738/article/details/102902629 文章目录一、背景二、原理三、API3.1. 直接完成任务3.2. 完成任务并且设置任务参数四、实现4.1. 接口4.2. 接口实现4…