网站关键词之间用什么符号隔开,wamp网站开发视频教程,企业网站开发成都,建立网站需要多少钱费用摘要#xff1a; 在开发中 unrecognized selector sent to instance XXXXX 是非常常见的 crash 类型。这篇博文主要介绍如何在客户端自修复该问题#xff0c;并进行原理解析。
作者介绍#xff1a;阿里云-移动云-大前端团队。
点此查看原文#xff1a;http://click.aliyun.…摘要 在开发中 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.4335730800 CYLSwizzleMainDemo[13252:156773356] -[NSNull fooDoesNotRecognizeSelector1]: unrecognized selector sent to instance 0x102870ef0
2018-01-11 16:28:04.4404360800 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
)
libcabi.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:返回nilRuntime则会发出-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.hinterface ForwardingTarget : NSObjectend
//
// ForwardingTarge.m
//
//
// Created by ChenYilong on 18/01/10.
// Copyright © 2018年 All rights reserved.
//#import ForwardingTarget.h
#import objc/runtime.himplementation 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
//
// NSObjectDoesNotRecognizeSelectorExtension.h
//
//
// Created by ChenYilong on 18/01/10.
// Copyright © 2018年 All rights reserved.
//#import Foundation/Foundation.hinterface NSObject (DoesNotRecognizeSelectorExtension)end
//
// NSObjectDoesNotRecognizeSelectorExtension.m
//
//
// Created by ChenYilong on 18/01/10.
// Copyright © 2018年 All rights reserved.
//#import NSObjectDoesNotRecognizeSelectorExtension.h
#import objc/runtime.h
#import ForwardingTarget.hstatic 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》