0%

TPreventUnrecognizedSEL实现思路以及原理

TPreventUnrecognizedSEL 项目的 GitHub 地址: https://github.com/tobedefined/TPreventUnrecognizedSEL
TPreventUnrecognizedSEL 的使用方法: LINK

关于方法的调用,ObjC对象以及类的结构,和unrecognized selector异常的产生流程的博文很多,这里只讲解和这个项目实现有关的重要部分,详细了解可以参考文末的参考文档。

objc_msgSend(id, SEL, ...)

在谈论这个之前,先写一段测试代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// main.m
#import <Foundation/Foundation.h>

#pragma mark - OBJ
@interface TestOBJ: NSObject
- (void)instanceMethod;
+ (void)classMethod;
@end

@implementation TestOBJ
- (void)instanceMethod {}
+ (void)classMethod {}
@end

#pragma mark - Main
int main(int argc, const char * argv[]) {
TestOBJ *obj = [[TestOBJ alloc] init];
[obj instanceMethod];
[TestOBJ classMethod];
return 0;
}

在命令行中运行clang -rewrite-objc main.m,在文件夹下生成main.cpp的C++文件,打开main.cpp找到C++的main函数(我的在第96634行),可以看到如下代码

1
2
3
4
5
6
int main(int argc, const char * argv[]) {
TestOBJ *obj = ((TestOBJ *(*)(id, SEL))(void *)objc_msgSend)((id)((TestOBJ *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestOBJ"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("instanceMethod"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestOBJ"), sel_registerName("classMethod"));
return 0;
}

这段代码中包含了很多的类型转换,现在将类型转换去掉,转变后的代码更容易阅读,如下:

1
2
3
4
5
6
7
8
9
int main(int argc, const char * argv[]) {
// 1
TestOBJ *obj = objc_msgSend(objc_msgSend(objc_getClass("TestOBJ"), sel_registerName("alloc")), sel_registerName("init"));
// 2
objc_msgSend(obj, sel_registerName("instanceMethod"));
// 3
objc_msgSend(objc_getClass("TestOBJ"), sel_registerName("classMethod"));
return 0;
}

首先看第二行代码objc_msgSend(obj, sel_registerName("instanceMethod"));,这段代码将[obj instanceMethod]; rewrite 成了这种方式,也就是说ObjC中中括号调用方法实际上是调用了OBJC_EXPORT id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)的方法,第一个参数传的是调用方法的对象,第二个参数传的是方法选择器。

然后看第三行代码objc_msgSend(objc_getClass("TestOBJ"), sel_registerName("classMethod"));,与上面类似,只是第一个含参数传的是调用方法的Class(他也就是调用类方法的对象)。

那么回头看第一行代码,就很容易理解了,TestOBJ *obj = objc_msgSend(objc_msgSend(objc_getClass("TestOBJ"), sel_registerName("alloc")), sel_registerName("init"));可以拆分成两个部分,如下,就不在讨论了

1
2
id obj_alloc = objc_msgSend(objc_getClass("TestOBJ"), sel_registerName("alloc"))
TestOBJ *obj = objc_msgSend(obj_alloc, sel_registerName("init"))

类是什么?

网上很多的博文都写到了,类也是一个对象,关于这块有很多资料。可以从runtime源码中看到类就是一个对象。

runtime可运行修改版GitHub地址 git@github.com:RetVal/objc-runtime.git, 下面代码摘自这个版本的runtime源码。

打开/Project Headers/objc-private.h文件可以看到objc_object的实现,我们用的oc对象其实就是一个objc_object

1
2
3
4
5
6
7
8
9
10
11
12
13
struct objc_object {
private:
isa_t isa;

public:

// ISA() assumes this is NOT a tagged pointer object
Class ISA();

// getIsa() allows this to be a tagged pointer object
Class getIsa();
......
}

打开/Project Headers/objc-runtime-new.h文件可以看到objc_class的实现,oc中的类就是objc_class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags

class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
......
}

可以看到objc_class是继承自objc_object的,所以objc_class也是一个对象是没错的。

这里我们关注下面这部分:(下面的代码进行了改写,比较容易理解混合了OBJC和OBJC2的代码,实际情况远比这复杂的多,详细了解参考文末的资料)

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
// 上述的runtime源码
// File: "/Project Headers/objc-private.h"
// Line: 48
struct objc_class;
struct objc_object;
typedef struct objc_class *Class;
typedef struct objc_object *id;

// 上述的runtime源码
// File: "/Project Headers/objc-private.h"
// Line: 168
struct objc_object {
isa_t isa;
};

// macOS 的runtime.h头文件和上述的runtime源码
// File: "/Project Headers/objc-runtime-new.h"
// Line: 1064
struct objc_class : objc_object {
Class superclass;
struct objc_method_list **methodLists;
};


// 上述的runtime源码
// File: "/Project Headers/objc-private.h"
// Line: 68
union isa_t {
Class cls;
}

可以看到以下重要的几点内容:

  • id 是一个指向 objc_object 的指针
  • Class 是一个指向 objc_class 的指针
  • objc_object 中包含 isa_t isa;
  • isa 中包含 Class cls;,cls 指向对应的Class
  • objc_class 继承自 objc_object,所以也属于 objc_object,同样也包含 isa,也指向一个 Class ,这个 Class 我们称之为元类,即 meta class
  • meta class 也是 Class,同样也包含 isa,它指向 NSObjectmeta class,另外,NSObject meta classisa 指向自身

这里有一张经典的图:

objc isa class diagram

方法的调用过程

关于 objc_class 中的 methodLists ,很明显, methodLists 就是函数列表,他存储着这个类的所有对象方法,没错, objc_classmethodLists 存储的是对象方法;而类方法则是存储在 meta classmethodLists 中。通俗的说,就是我们写的 - 号开头的方法(对象方法),则加入 ClassmethodLists 之中;+ 写的方法(类方法),加入 meta classmethodLists 之中。

对象方法和类方法的调用运行走的是同一套流程。

对象方法

  • 转换成 objc_msgSend(obj, sel, ...) 形式
  • 对象调用方法时,首先通过 isa 找到所属的类,然后获取 methodLists 中对应的 sel 的函数的地址
  • 运行函数

类方法

  • 转换成 objc_msgSend(cls, sel, ...) 形式
  • 类调用类方法时,首先通过该类的 isa 找到所属的元类,然后获取 methodLists 中对应的 sel 的函数的地址
  • 运行函数

Unrecognized Selector 错误

对象方法:*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TestClass losted:instance:method:]: unrecognized selector sent to instance 0x102c....'

类方法:*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[TestClass losted:class:method:]: unrecognized selector sent to class 0x10000....'

众所周知,产生 Unrecognized Selector 错误之前,系统会给出多次机会让用户进行添加方法或转发,一般分为两个步骤(Method ResolutionForwarding)或三个步骤(Method ResolutionFast ForwardingNormal Forwarding

  • Method Resolution
    ObjC运行时会调用 + (BOOL)resolveInstanceMethod:(SEL)sel 或者 + (BOOL)resolveClassMethod:(SEL)sel,让你有机会提供一个函数实现添加为对象方法或类方法。添加了函数之后,返回YES,系统就会重新启动一次消息发送的过程,如果返回NO,系统就会开始进行消息转发的过程。

  • Fast Forwarding
    快速转发过程,- (id)forwardingTargetForSelector:(SEL)aSelector,系统会调用这个方法对对象方法的调用进行转发,如果返回一个newObject,则相当于转发后运行 [newObject performSelector:aSelector]。如果返回的是nil,则进入默认转发的过程。

  • Normal Forwarding
    这一步是runtime的最后一次转发过程。在这一个过程中,首先调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector获取SEL的函数签名,这个时候你应该返回一个存在的已经函数签名,如果返回的是nil,此时runtime就会发出- (void)doesNotRecognizeSelector:(SEL)aSelector消息,这个时候,程序就会crash崩溃掉了。如果返回了一个函数签名,就会根据这个函数签名创建一个NSInvocation的对象并发送- (void)forwardInvocation:(NSInvocation *)anInvocation消息给目标对象。

我们主要通过Forwarding的两步操作进行防止产生 Unrecognized Selector 错误(Method Resolution过程在runtime中经常会进行主动去掉用,如果使用Resolution,可能产生死循环)。但是可以发现,-forwardingTargetForSelector:-methodSignatureForSelector:-forwardInvocation: 都只存在对象方法,而不存在类方法。这时候改如何解决?

回到前面,类其实也是一个对象,是元类(meta class)的对象,而元类(meta class)的结构是和Class的结构是相同的,都是objc_class,而类方法的调用和对象方法的调用走的是同一个流程,则我们添加这几个方法到元类(meta class)中,就可以解决类方法不存在的流程;即将-forwardingTargetForSelector:-methodSignatureForSelector:-forwardInvocation: 写作 +forwardingTargetForSelector:+methodSignatureForSelector:+forwardInvocation: 既可以添加到元类(meta class)的 methodLists之中

关于一些runtime的函数以及作用

class_getName

1
2
OBJC_EXPORT const char * _Nonnull
class_getName(Class _Nullable cls);

class_getName方法是获取Class的 C String,相当于C语言版本的 NSString *NSStringFromClass(Class aClass)

objc_getClass

1
2
OBJC_EXPORT Class _Nullable
objc_getClass(const char * _Nonnull name);

objc_getClass方法是获取根据 C String 获取一个 Class,相当于C语言版本的 Class _Nullable NSClassFromString(NSString *aClassName)

objc_getMetaClass

1
2
OBJC_EXPORT Class _Nullable
objc_getMetaClass(const char * _Nonnull name);

objc_getMetaClass方法是获取根据 C string 获取这个 String 所表示的 Class 的 MetaClass
比如以下代码:

1
2
3
char myClassName[] = "MyClass";
Class myClass = objc_getClass(myClassName);
Class myMetaClass = objc_getMetaClass(myClassName);

其中 myClassMyClass 这个类,myMetaClassMyClass 这个类的元类
所以获取当前类的元类可以使用objc_getMetaClass(class_getName(self)) 或者 objc_getMetaClass(class_getName([self class]))

class_getInstanceMethod

1
2
OBJC_EXPORT Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);

返回Class的对象方法SEL的函数指针

class_getClassMethod

1
2
OBJC_EXPORT Method _Nullable
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name);

返回Class的类方法SEL的函数指针,等同于class_getInstanceMethod(objc_getMetaClass(class_getName(self)), name)

method_exchangeImplementations

1
2
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);

用于替换两个函数的实现。

比如以下代码中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@implementation MyOBJ
+ (void)load {
method_exchangeImplementations(class_getInstanceMethod(self, @selector(instanceMethodOne)),
class_getInstanceMethod(self, @selector(instanceMethodTwo)));
}

- (void)instanceMethodOne {
// 函数体1
}

- (void)instanceMethodTwo {
// 函数体2
}
@end

-instanceMethodOne将与-instanceMethodTwo将会指向对方的函数体,也就是说[obj instanceMethodOne]其实是调用的函数体2,[obj instanceMethodTwo]其实是调用的函数体1。

class_addMethod

1
2
3
OBJC_EXPORT BOOL
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types);

Class cls 添加名为 nameselector,指向的函数实现为 imp, 函数的 Type Encodingtypes

Type Encoding 可以参考:Type Encoding

关于+load

load方法的调用规则:

  • 调用顺序 父类 > 子类 > 分类
  • 调用+load之前先调用父类的+load,这个过程自动实现,不需要手动添加[super load];
  • 如果一个类没有实现+load方法,不会沿用父类的实现
  • 一个+load函数只会执行一次
  • 类和分类都执行中的+load都会执行
  • 调用时机:类被添加到 runtime 时(对于动态库和静态库中的class和category都有效)

实现

HandleUnrecognizedSELErrorBlock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

// NSObject+TPUSELFastForwarding.h 或 NSObject+TPUSELNormalForwarding.h
typedef NS_ENUM(NSUInteger, UnrecognizedMethodType) {
UnrecognizedMethodTypeClassMethod = 1,
UnrecognizedMethodTypeInstanceMethod = 2,
};

typedef void (^ __nullable HandleUnrecognizedSELErrorBlock)(Class cls, SEL selector, UnrecognizedMethodType methodType);


// NSObject+TPUSELFastForwarding.m 或 NSObject+TPUSELNormalForwarding.m

@implementation NSObject (ADD)

+ (void)setHandleUnrecognizedSELErrorBlock:(HandleUnrecognizedSELErrorBlock)handleBlock {
objc_setAssociatedObject(self, @selector(handleUnrecognizedSELErrorBlock), handleBlock, OBJC_ASSOCIATION_RETAIN);
}

+ (HandleUnrecognizedSELErrorBlock)handleUnrecognizedSELErrorBlock {
return objc_getAssociatedObject(self, @selector(handleUnrecognizedSELErrorBlock));
}
@end

用于向外部传出所丢失函数的具体错误信息,向 NSObjectClass对象添加关联block,并在在适当的时候调用,具体参数的信息如下

  • cls: Class类型;为缺失方法的类或对象的Class,可使用NSStringFromClass(cls)返回类名字符串
  • selector: SEL类型;为所缺失的方法名,可使用NSStringFromSelector(selector)返回方法名的字符串
  • methodType: UnrecognizedMethodType类型;为所缺失的方法类型(类方法or对象方法)

TPUSELFastForwarding

核心代码如下:

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// NSObject+TPUSELFastForwarding.m


void __c_t_resolveLostedMethod(id self, SEL _cmd, ...) {}

@implementation NSObject (TPUSELFastForwarding)

#pragma mark - HandleUnrecognizedSELErrorBlock



+ (void)setJustForwardClassArray:(NSArray *)forwardClassArray
handleUnrecognizedSELErrorBlock:(HandleUnrecognizedSELErrorBlock)handleBlock {
objc_setAssociatedObject(self,
@selector(handleUnrecognizedSELErrorBlock),
handleBlock,
OBJC_ASSOCIATION_RETAIN);
objc_setAssociatedObject(self,
@selector(justForwardClassArray),
forwardClassArray,
OBJC_ASSOCIATION_RETAIN);
}

+ (HandleUnrecognizedSELErrorBlock)handleUnrecognizedSELErrorBlock {
return objc_getAssociatedObject(self,
_cmd);
}

+ (NSArray *)justForwardClassArray {
return (NSArray *)objc_getAssociatedObject(self,
_cmd);
}

+ (BOOL)isCanFowardingFor:(Class)cls {
// is setting justForwardClassArray, will just forward the class(and subclass) inside of the justForwardClassArray
for (NSObject *element in [NSObject justForwardClassArray]) {
Class justForwardCls;
if ([element isKindOfClass:[NSString class]]) {
justForwardCls = NSClassFromString((NSString *)element);
} else {
justForwardCls = (Class)element;
}
if ([cls isSubclassOfClass:justForwardCls]) {
return YES;
}
}
return NO;
}

+ (Class)getProtectorClass {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class protectorCls = objc_allocateClassPair([NSObject class], "__TProtectorClass", 0);
objc_registerClassPair(protectorCls);
});
Class protectorCls = NSClassFromString(@"__TProtectorClass");
return protectorCls;
}


+ (instancetype)getProtectorInstance {
static id __t_protector_instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__t_protector_instance = [[[self getProtectorClass] alloc] init];
});
return __t_protector_instance;
}

#pragma mark - FastForwarding

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
method_exchangeImplementations(class_getInstanceMethod(self, @selector(forwardingTargetForSelector:)),
class_getInstanceMethod(self, @selector(__t_forwardingTargetForSelector:)));
method_exchangeImplementations(class_getClassMethod(self, @selector(forwardingTargetForSelector:)),
class_getClassMethod(self, @selector(__t_forwardingTargetForSelector:)));
});
}

- (id)__t_forwardingTargetForSelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {
return [self __t_forwardingTargetForSelector:aSelector];
}

if ([NSObject isCanFowardingFor:[self class]]) {
class_addMethod([NSObject getProtectorClass],
aSelector,
(IMP)__c_t_resolveLostedMethod,
"v@:");
HandleUnrecognizedSELErrorBlock handleBlock = [NSObject handleUnrecognizedSELErrorBlock];
if (handleBlock != nil) {
NSArray <NSString *>*callStackSymbols = @[@"The system version is too low."];
if (@available(iOS 4.0, tvOS 9.0, macOS 10.6, watchOS 2.0, *)) {
callStackSymbols = [NSThread callStackSymbols];
}
handleBlock([self class], aSelector, UnrecognizedMethodTypeInstanceMethod, callStackSymbols);
}

return [NSObject getProtectorInstance];
}
return [self __t_forwardingTargetForSelector:aSelector];
}

+ (id)__t_forwardingTargetForSelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {
return [self __t_forwardingTargetForSelector:aSelector];
}

if ([NSObject isCanFowardingFor:self]) {
class_addMethod(objc_getMetaClass(class_getName([NSObject getProtectorClass])),
aSelector,
(IMP)__c_t_resolveLostedMethod,
"v@:");
HandleUnrecognizedSELErrorBlock handleBlock = [NSObject handleUnrecognizedSELErrorBlock];
if (handleBlock != nil) {
NSArray <NSString *>*callStackSymbols = @[@"The system version is too low."];
if (@available(iOS 4.0, tvOS 9.0, macOS 10.6, watchOS 2.0, *)) {
callStackSymbols = [NSThread callStackSymbols];
}
handleBlock([self class], aSelector, UnrecognizedMethodTypeClassMethod, callStackSymbols);
}

return [NSObject getProtectorClass];
}
return [self __t_forwardingTargetForSelector:aSelector];
}

@end

思路:

  • +load中替换forwardingTargetForSelector:方法(类中和元类中都进行替换)
  • [NSObject getProtectorClass][NSObject getProtectorInstance] 获得一个承载添加方法的类和对象,用于添加缺失的方法和方法调用
  • __t_forwardingTargetForSelector:中:
    • 判断本类是否包含aSelector,如果包含,返回自身;若不包含走下一步。
    • 判断是否允许处理该类型的类,如果不允许,不进行处理,调用__t_forwardingTargetForSelector去执行原函数;如果允许,走下一步
    • 使用 class_addMethod[NSObject getProtectorClass] 添加方法的实现(解决对象方法缺失问题)或对 [NSObject getProtectorClass] 的元类添加方法的实现(解决类方法缺失问题);
    • 获取 handleBlock,调用block将具体信息传出去。
    • 返回 [NSObject getProtectorInstance] (解决对象方法缺失问题)或者 [NSObject getProtectorClass]Class对象, 解决类方法缺失问题)

TPUSELNormalForwarding

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// NSObject+TPUSELNormalForwarding.m

@implementation NSObject (TPUSELNormalForwarding)

#pragma mark - HandleUnrecognizedSELErrorBlock

+ (void)setHandleUnrecognizedSELErrorBlock:(HandleUnrecognizedSELErrorBlock)handleBlock {
objc_setAssociatedObject(self,
@selector(handleUnrecognizedSELErrorBlock),
handleBlock,
OBJC_ASSOCIATION_RETAIN);
}

+ (HandleUnrecognizedSELErrorBlock)handleUnrecognizedSELErrorBlock {
return objc_getAssociatedObject(self,
_cmd);
}

+ (void)setJustForwardClassArray:(NSArray<NSString *> *)forwardClassArray; {
objc_setAssociatedObject(self,
@selector(justForwardClassArray),
forwardClassArray,
OBJC_ASSOCIATION_RETAIN);
}

+ (NSArray *)justForwardClassArray {
return (NSArray<NSString *> *)objc_getAssociatedObject(self,
_cmd);
}

+ (void)setIgnoreForwardNSNullClass:(BOOL)ignoreNSNull {
objc_setAssociatedObject(self,
@selector(ignoreForwardNSNullClass),
[NSNumber numberWithBool:ignoreNSNull],
OBJC_ASSOCIATION_RETAIN);
}

+ (BOOL)ignoreForwardNSNullClass {
return [(NSNumber *)objc_getAssociatedObject(self,
_cmd)
boolValue];
}

+ (void)setIgnoreForwardClassArray:(NSArray<NSString *> *)ignoreClassArray {
objc_setAssociatedObject(self,
@selector(ignoreForwardClassArray),
ignoreClassArray,
OBJC_ASSOCIATION_RETAIN);
}

+ (NSArray *)ignoreForwardClassArray {
return (NSArray *)objc_getAssociatedObject(self,
_cmd);
}

+ (BOOL)isCanFowardingFor:(Class)cls {
if ([NSObject ignoreForwardNSNullClass] && [cls isSubclassOfClass:[NSNull class]]) {
return NO;
}

for (NSObject *element in [NSObject ignoreForwardClassArray]) {
Class ignoreCls;
if ([element isKindOfClass:[NSString class]]) {
ignoreCls = NSClassFromString((NSString *)element);
} else {
ignoreCls = (Class)element;
}
if ([cls isSubclassOfClass:ignoreCls]) {
return NO;
}
}

// is setting justForwardClassArray, will just forward the class(and subclass) inside of the justForwardClassArray
NSArray *justForwardClassArray = [NSObject justForwardClassArray];
if (justForwardClassArray.count > 0) {
for (NSObject *element in justForwardClassArray) {
Class justForwardCls;
if ([element isKindOfClass:[NSString class]]) {
justForwardCls = NSClassFromString((NSString *)element);
} else {
justForwardCls = (Class)element;
}
if ([cls isSubclassOfClass:justForwardCls]) {
return YES;
}
}
return NO;
}
return YES;
}


#pragma mark - ForwardInvocation

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
method_exchangeImplementations(class_getInstanceMethod(self, @selector(methodSignatureForSelector:)),
class_getInstanceMethod(self, @selector(__t_methodSignatureForSelector:)));
method_exchangeImplementations(class_getInstanceMethod(self, @selector(forwardInvocation:)),
class_getInstanceMethod(self, @selector(__t_forwardInvocation:)));
method_exchangeImplementations(class_getClassMethod(self, @selector(methodSignatureForSelector:)),
class_getClassMethod(self, @selector(__t_methodSignatureForSelector:)));
method_exchangeImplementations(class_getClassMethod(self, @selector(forwardInvocation:)),
class_getClassMethod(self, @selector(__t_forwardInvocation:)));
});
}

- (NSMethodSignature *)__t_methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [self __t_methodSignatureForSelector:aSelector];
if (signature || [self respondsToSelector:aSelector]) {
return signature;
}

if (![NSObject isCanFowardingFor:[self class]]) {
return signature;
}

HandleUnrecognizedSELErrorBlock handleBlock = [NSObject handleUnrecognizedSELErrorBlock];
if (handleBlock != nil) {
NSArray <NSString *>*callStackSymbols = @[@"The system version is too low."];
if (@available(iOS 4.0, tvOS 9.0, macOS 10.6, watchOS 2.0, *)) {
callStackSymbols = [NSThread callStackSymbols];
}
handleBlock([self class], aSelector, UnrecognizedMethodTypeInstanceMethod, callStackSymbols);
}

return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)__t_forwardInvocation:(NSInvocation *)anInvocation {
if (![NSObject isCanFowardingFor:[self class]]) {
return [self __t_forwardInvocation:anInvocation];
}

SEL selector = [anInvocation selector];
if ([self respondsToSelector:selector]) {
[anInvocation invokeWithTarget:self];
}
}

+ (NSMethodSignature *)__t_methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [self __t_methodSignatureForSelector:aSelector];
if (signature || [self respondsToSelector:aSelector]) {
return signature;
}

if (![NSObject isCanFowardingFor:self]) {
return signature;
}

HandleUnrecognizedSELErrorBlock handleBlock = [NSObject handleUnrecognizedSELErrorBlock];
if (handleBlock != nil) {
NSArray <NSString *>*callStackSymbols = @[@"The system version is too low."];
if (@available(iOS 4.0, tvOS 9.0, macOS 10.6, watchOS 2.0, *)) {
callStackSymbols = [NSThread callStackSymbols];
}
handleBlock([self class], aSelector, UnrecognizedMethodTypeClassMethod, callStackSymbols);
}

return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

+ (void)__t_forwardInvocation:(NSInvocation *)anInvocation {
if (![NSObject isCanFowardingFor:self]) {
return [self __t_forwardInvocation:anInvocation];
}

SEL selector = [anInvocation selector];
if ([self respondsToSelector:selector]) {
[anInvocation invokeWithTarget:self];
}
}

@end

思路:

  • +load中替换methodSignatureForSelector:forwardInvocation:方法(类中和元类中都进行替换)
  • __t_methodSignatureForSelector:中:
    • 判断本类是否包含aSelector,如果包含,不进行处理,调用__t_methodSignatureForSelector去执行原函数;如果不包含,走下一步
    • 判断是否允许处理该类型的类,如果不允许,不进行处理,调用__t_methodSignatureForSelector去执行原函数;如果允许,走下一步
    • 获取 handleBlock,调用block将具体信息传出去。
    • 创建 NSMethodSignature 并返回
  • __t_forwardInvocation:
    • 判断是否允许处理该类型的类,如果不允许,不进行处理,调用__t_forwardInvocation去执行原函数;如果允许,走下一步
    • 如果现在自己包含了对应的selector,则执行[anInvocation invokeWithTarget:self];;如果不包含不进行anInvocation的执行

具体详见GitHub代码

参考资料:

runtime可运行修改版GitHub地址

从 NSObject 的初始化了解 isa

NSObject 的 isa 和 Class(ObjC 1.0 和 ObjC 2.0)

深入解析 ObjC 中方法的结构

从源代码看 ObjC 中消息的发送

Type Encoding

Apple Doc: load

Objective-C +load vs +initialize

Objective-C Runtime