博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
预防 app crash 之 unrecognized selector
阅读量:5758 次
发布时间:2019-06-18

本文共 7321 字,大约阅读时间需要 24 分钟。

处理unrecognized selector异常原因

假如封装一个方法,在其他模块调用该方法时,传入参数不匹配则crash。比如下面的方法:本应该传入的参数类型为NSMutableArray,如果传入的参数类型是NSArray,导致抛出 unrecognized selector异常

1
2
3
- (
void)doSomethingWithArray:(NSMutableArray *)arr{
[arr addObject:
@"123"];
}

当然,通过 参数类型判断 也可以避免问题的发生:

1
2
3
4
5
6
7
- (
void)doSomethingWithArray:(NSMutableArray *)arr{
if ([arr isKindOfClass:[NSMutableArray class]]) {
[arr addObject:
@"123"];
}
else{
CrashOnSimulator(
@"⚠️参数类型不对哦⚠️");
}
}

crash提醒:

1
2
3
void CrashOnSimulator(NSString *errorMsg) {
if((TARGET_OS_SIMULATOR)){raise(SIGSTOP);}
}

但是,有点地方可能忘记类型判断了怎么办,有全局拦截unrecognized selector 异常的方案吗?

分析 如何全局拦截unrecognized selector 异常

oc的消息发送机制咱们都熟悉了,通过superclass指针逐级向上查找该消息所对应的方法实现,如果遇到找不的方法,还有三次补救机制。我们可以通过上面三种方法中的一种,就可以避免unrecognized selector sent to instance

第一种方法:重写 NSObject 的forwardingTargetForSelector:

⚠️filter unrecoginze seletor of intance only

思路

  • 创建一个接收未知消息的类,暂且称之为Protector
  • 创建一个NSObject 的分类,在分类中重写forwardingTargetForSelector: ,在这个方法中截获未实现的方法,转发给Protector。并为Protector 动态的添加未实现的方法,最后返回Protector 的实例对象。
  • 在分类中新增一个安全的方法实现,来作为Protector 接收到的未知消息的实现

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#import "NSObject+Protector.h"
#import <objc/runtime.h>
@implementation NSObject (Protector)
 
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
- (
id)forwardingTargetForSelector:(SEL)aSelector{
 
if ([self isCurrentClassInWhiteList]) {
[[
self class] warningDeveloper:aSelector];
 
Class protectorCls =
NSClassFromString(@"ProtectorClassName");
if (!protectorCls){
protectorCls = objc_allocateClassPair([
NSObject class], "ProtectorClassName", 0);
objc_registerClassPair(protectorCls);
}
 
if (![self isExistSelector:aSelector inClass:protectorCls]){
class_addMethod(protectorCls, aSelector, [
self safeImplementation:aSelector],[NSStringFromSelector(aSelector) UTF8String]);
}
 
Class Protector = [protectorCls
class];
id instance = [[Protector alloc] init];
return instance;
}
else {
return nil;
}
}
#pragma clang diagnostic pop
 
- (
BOOL)isCurrentClassInWhiteList{
NSArray *classNameArray = @[@"NSNull",@"NSString",@"NSArray",@"NSDictionary",@"NSURL"];
for (NSString *className in classNameArray) {
if ([self isKindOfClass:NSClassFromString(className)]) {
return YES;
}
}
return NO;
}
 
- (
BOOL)isExistSelector: (SEL)aSelector inClass:(Class)currentClass{
BOOL isExist = NO;
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(currentClass, &methodCount);
for (int i = 0; i < methodCount; i++){
Method temp = methods[i];
SEL sel = method_getName(temp);
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString: NSStringFromSelector(aSelector)]){
isExist =
YES;
break;
}
}
return isExist;
}
 
- (IMP)safeImplementation:(SEL)aSelector{
IMP imp = imp_implementationWithBlock(^(){
NSLog(@"PROTECTOR: %@ Done", NSStringFromSelector(aSelector));
});
return imp;
}
 
+ (
void)warningDeveloper:(SEL)aSelector{
#if DEBUG
NSString *selectorStr = NSStringFromSelector(aSelector);
NSLog(@"PROTECTOR: -[%@ %@]", [self class], selectorStr);
NSLog(@"PROTECTOR: unrecognized selector \"%@\" sent to instance: %p", selectorStr, self);
NSLog(@"PROTECTOR: call stack: %@", [NSThread callStackSymbols]);
// @throw @"方法找不到";
#endif
}
 
@end

第二种方法:重写 NSObject 的methodSignatureForSelector(有些问题,下面有个最终版)

?filter unrecoginze seletor of class and intance

but 如果你使用了JSPatch、Aspects等对methodSignatureForSelector进行swizzle的第三方库,就别用这种方案了,有冲突,出现莫名的错误

思路

  • 创建NSObject+Protector重写methodSignatureForSelector,判断current class是否在白名单?
    • YES:返回一个空签名,啥也不做
    • NO:返回正常的签名,走原来的逻辑

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#import "NSObject+Protector.h"
#import <objc/runtime.h>
@implementation NSObject (Protector)
 
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [[self class] __getMethodSignatureForSelector:aSelector];
}
 
- (void)forwardInvocation:(NSInvocation *)anInvocation{
}
#pragma clang diagnostic pop
 
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [self __getMethodSignatureForSelector:aSelector];
}
 
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
}
 
+ (NSMethodSignature *)__getMethodSignatureForSelector:(SEL)aSelector{
if ([self isSubclassInWhiteListClass]) {
[self warningDeveloper:aSelector];
return [NSMethodSignature signatureWithObjCTypes:"@"];
}else{
return [self instanceMethodSignatureForSelector:aSelector];
}
}
 
+ (BOOL)isSubclassInWhiteListClass{
NSArray *classNameArray = @[@"NSNull",@"NSString",@"NSArray",@"NSDictionary",@"NSURL"];
for (NSString *className in classNameArray) {
if ([self isSubclassOfClass:NSClassFromString(className)]) {
return YES;
}
}
return NO;
}
 
+ (void)warningDeveloper:(SEL)aSelector{
#if DEBUG
NSString *selectorStr = NSStringFromSelector(aSelector);
NSLog(@"PROTECTOR: -[%@ %@]", [self class], selectorStr);
NSLog(@"PROTECTOR: unrecognized selector \"%@\" sent to instance: %p", selectorStr, self);
NSLog(@"PROTECTOR: call stack: %@", [NSThread callStackSymbols]);
// @throw @"方法找不到";
#endif
}

第三种方法:最终方案(解决methodSignatureForSelector的不足)

第三方库对 methodSignatureForSelector进行了全局替换,而我们也在NSObject中 进行了全局替换,冲突的点在于我们影响了第三库的自定义的Class。<br\>

  • 如何避免呢? 我们替换常用的的几个class就行了呗,是的,不过工作量有点大且重复,怎么办?用 define 来解决,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// NSArray+WBGProtector.m
 
#import "NSArray+WBGProtector.h"
#import <objc/runtime.h>
 
#define WBG_PROTECT_CLASS_NAME(_classname_)\
@implementation _classname_ (WBGProtector)\
\
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{\
return [[self class] __getMethodSignatureForSelector:aSelector type:@"instance"];\
}\
\
- (void)forwardInvocation:(NSInvocation *)anInvocation{\
}\
\
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{\
return [self __getMethodSignatureForSelector:aSelector type:@"class"];\
}\
\
+ (void)forwardInvocation:(NSInvocation *)anInvocation{\
}\
\
+ (NSMethodSignature *)__getMethodSignatureForSelector:(SEL)aSelector type:(id)type{\
NSString *errorMsg = [NSString stringWithFormat:@"PROTECTOR: -[%@ %@],unrecognized selector sent to %@",self,NSStringFromSelector(aSelector),type];\
NSLog(@"%@",errorMsg);\
CrashOnSimulator(errorMsg);\
return [NSMethodSignature signatureWithObjCTypes:"@"];\
}\
\
@end\
 
 
WBG_PROTECT_CLASS_NAME(NSArray)
WBG_PROTECT_CLASS_NAME(NSDictionary)
WBG_PROTECT_CLASS_NAME(NSString)

细节分析

为什么需要白名单?

app启动加载一些系统方法,总是莫名其名的 报错 甚至crash

为什么去掉UIResponder?

isCurrentClassInWhiteList 中的classNameArray 本来是有 UIResponder的,但是后来测试发现UIWebView会出现异常!这里把UIResponder去掉了,毕竟过滤大部分的unrecognize selector主要的是NSArrayNSDictionary

测试code

1
2
3
4
5
6
7
// test Class method
id clazz = [NSArray class];
[clazz viewDidLoad];
 
// test instance method
NSMutableArray *arr = @{};
[arr addObject:@""]

遗留问题

    • 需要判断自己项目中引入的第三方库没有通过category的方式去重写NSObject的方法methodSignatureForSelector /forwardInvocation 以及forwardingTargetForSelector
      • 原因:一个category也不能可靠的覆盖另一个category中相同的类的相同的方法。例如UIViewController+A与UIViewController+B,都重写了viewDidLoad,我们就无法控制谁覆盖了谁。
    • 如果第三方重写了,则在这里通过swizzling的方式 替换 具体的实现方法

转载地址:http://isvkx.baihongyu.com/

你可能感兴趣的文章
15分钟构建超低成本数据大屏:DataV + DLA
查看>>
南大领衔!国内高校团队登上美国《科学进展》杂质,发布基因编辑可控技术...
查看>>
当下一对一直播源码市场空间
查看>>
1月9日云栖精选夜读 | Mars 算法实践——人脸识别
查看>>
.NET快速开发平台核心优势
查看>>
《黑匣子思维》读后感
查看>>
最全技术面试180题:阿里11面试+网易+百度+美团!
查看>>
SparkSQL Catalyst解析
查看>>
jSearch(聚搜) 1.0.0 终于来了
查看>>
Java字节码结构剖析二:字段表
查看>>
盘点2018云计算市场,变化大于需求?
查看>>
极光推送(一)集成
查看>>
Android项目实战(三十九):Android集成Unity3D项目(图文详解)
查看>>
MySQL 8.0 压缩包版安装方法
查看>>
TensorFlow系列专题(六):实战项目Mnist手写数据集识别
查看>>
JS中this的4种绑定规则
查看>>
Netty Pipeline源码分析(2)
查看>>
@Transient注解输出空间位置属性
查看>>
Ansible-playbook 条件判断when、pause(学习笔记二十三)
查看>>
开发者报 | Github造假产业链曝光,花钱就能买Star;黑客又多一个可以偷你密码的方法了...
查看>>