消息传递和消息转发-从源代码看起

第一部分:从方法调用得出的objc_msgSend消息传递的结论

1.1 文字的描述:先来看一段代码:
  1. ClangClass类有个handle方法
  2. handle方法里面是创建AClass对象并调用addO方法
  3. 我们来看看调用addO方法的具体过程

1.2 源代码的描述:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import "ClangClass.h"
#import "AClass.h"

@interface ClangClass()
@property(nonatomic,strong)AClass *acClass;
@end

@implementation ClangClass

- (instancetype)init{
if (self = [super init]) {
_acClass = [[AClass alloc] init];
}

return self;
}

- (void)handle{
[_acClass addO];
}

@end

1.3 将代码操作一下:
  1. 进入到上面ClangClass.m的文件夹下面,使用命令编译,命令为:clang -rewrite-objc ClangClass.m
  2. 看文件ClangClass.cpp得到的结果为,我们具体看调用的代码是:[_acClass addO];
  3. {是不是感觉一大堆很乱,我也觉得}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void _I_ClangClass_handle(ClangClass * self, SEL _cmd) {
((void (*)(id, SEL))(void *)objc_msgSend)((id)(*(AClass **)((char *)self + OBJC_IVAR_$_ClangClass$_acClass)), sel_registerName("addO"));
}

// 把上面代码优化下,去掉强制转换的符号得到
objc_msgSend(_acClass, sel_registerName("addO"));

---------------------------------------------

IMP本质:是一个函数指针,保存了方法地址,指向方法的实现,这是由编译器生成的。
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id

---------------------------------------------
得出结论:
1. 调用方法addO就是调用objc_msgSend进行消息传递;
2. 而objc_msgSend就是去找到IMP,来执行实现的代码;

第二部分:消息传递objc_msgSend、objc_msgSendSuper、objc_msgSend_stret、objc_msgSendSuper_stret

objc_msgSend定义:id objc_msgSend(id self, SEL op, …);

1
2
3
self:A pointer that points to the instance of the class that is to receive the message.{消息的接收者}
op:The selector of the method that handles the message.{是selector}
…:A variable argument list containing the arguments to the method.{是参数列表}

id的本质:id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员,根据isa指针就可以顺藤摸瓜找到对象所属的类

1
2
3
4
typedef struct objc_object *id;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
}


SEL的本质:SEL 是系统在编译过程中,根据方法的名字以及参数序列生成一个用来区分这个方法的唯一ID编号;这个ID就是 SEL 类型的。和C的函数指针还不一样,函数指针直接保存了方法的地址,但是SEL只是方法编号。

1
Selector(typedef struct objc_selector *SEL)


2.1 文字描述objc_msgSend的工作流程:
  1. 在self中沿着isa找到AClass的类对象;
  2. 优先在AClass的cache查找addO方法,如果有就返回imp;
  3. 如果第一次没找到缓存中的addO方法,就查看这个类有没有被释放,如果释放了,就返回;
  4. 如果这个类没有被释放,就检查这个类有没有实现 +initialize方法,如果有但是没有初始化,就初始化;
  5. 判断初始化后,尝试再在AClass的cache查找addO方法,如果找不到,就到AClass的methodLists查找addO方法;
  6. 如果没有在AClass找到addO方法,就会到super_class就是NSObject里面的cache查找;如果在父类的cache找不到,就到父类的methodLists中查找addO方法;
  7. 如果在父类的methodLists也没有找到addO方法,且如果强制传入resolver为YES,且找不到imp,尝试一次_class_resolveMethod,不尝试消息转发了。
  8. 如果resolver设置为NO,且还没有找到IMP,就返回_objc_msgForward_impcache开始消息转发;
  9. 不管上面的哪一步找到imp,都直接返回imp。

2.2 相关:
  • _objc_msgForward是用于消息转发的。这个函数的实现并没有在objc-runtime的开源代码里面,而是在Foundation框架里面实现的。加上断点启动程序后,会发现__CFInitialize这个方法会调用objc_setForwardHandler函数来注册一个实现。
  • 当向一般对象发送消息时,调用objc_msgSend;当向super发送消息时,调用的是objc_msgSendSuper; 如果返回值是一个结构体,则会调用objc_msgSend_stret或objc_msgSendSuper_stret。

2.3 本质:
  • objc_msgSend的本质就是找到imp指针执行代码,而静态语言的imp函数指针在编译的时候就已经确定好了,动态语言是编译后可以变换的。

2.4 源代码描述objc_msgSend的工作流程:
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
{其实我没有找到这段的实现,就用大神的代码了}
id objc_msgSend(id self, SEL op, ...) {
if (!self) return nil;
IMP imp = class_getMethodImplementation(self->isa, SEL op);
imp(self, op, ...); //调用这个函数,伪代码...
}

// 源码
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;

if (!cls || !sel) return nil;

imp = lookUpImpOrNil(cls, sel, nil,
YES/*initialize*/, YES/*cache*/, YES/*resolver*/);

// Translate forwarding function to C-callable external version
if (!imp) {
// 如果找不到imp就返回消息转发
return _objc_msgForward;
}

return imp;
}

IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
// 查找imp或决定转发
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}

IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
Class curClass;
IMP methodPC = nil;
Method meth;
bool triedResolver = NO;

methodListLock.assertUnlocked();

// Optimistic cache lookup
if (cache) {
// 查找imp,如果有缓存,就返回缓存
methodPC = _cache_getImp(cls, sel);
if (methodPC) return methodPC;
}

// Check for freed class
// 查看释放的类
if (cls == _class_getFreedObjectClass())
// 如果类已经释放,返回_freedHandler
return (IMP) _freedHandler;

// Check for +initialize
// 检查有没有实现+initialize方法
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}

// The lock is held to make method-lookup + cache-fill atomic
// with respect to method addition. Otherwise, a category could
// be added but ignored indefinitely because the cache was re-filled
// with the old value after the cache flush on behalf of the category.
retry:
// 加锁
methodListLock.lock();

// Try this class's cache.
// 查找类的缓存
methodPC = _cache_getImp(cls, sel);
if (methodPC) goto done;

// Try this class's method lists.
// 查找类的方法列表
meth = _class_getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, cls, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}

// Try superclass caches and method lists.

curClass = cls;
while ((curClass = curClass->superclass)) {
// Superclass cache.
// 查找父类的缓存
meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache);
if (meth) {
if (meth != (Method)1) {
// Found the method in a superclass. Cache it in this class.
// 在父类的缓存中查找
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}

// Superclass method list.
// 查找父类的方法列表
meth = _class_getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
}

// No implementation found. Try method resolver once.

if (resolver && !triedResolver) {
// 解锁
methodListLock.unlock();
// 如果强制传入resolver为YES,且找不到imp,尝试一次_class_resolveMethod,不尝试消息转发了
_class_resolveMethod(cls, sel, inst);
triedResolver = YES;
goto retry;
}

// No implementation found, and method resolver didn't help.
// Use forwarding.
// 方法找不到:尝试一次resolver方法无效;开始转发
_cache_addForwardEntry(cls, sel);
// 返回_objc_msgForward_impcache表示转发
methodPC = _objc_msgForward_impcache;

done:
// 解锁
methodListLock.unlock();

return methodPC;
}

第三部分:消息转发_objc_msgForward

1
2
3
4
5
// add1方法是不存在的,现在试试转发
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
AClass *acClass = [[AClass alloc] init];
[acClass performSelector:@selector(add1)];
}

3.1 oc转发文字描述:
  1. 调用resolveInstanceMethod:方法。允许用户在此时为该Class动态添加实现。如果有实现了,则调用并返回YES,重新开始objc_msgSend流程。这次对象会响应这个选择器,一般是因为它已经调用过了class_addMethod。如果仍没有实现,继续下面的动作。
  2. 如果调用resolveInstanceMethod没有实现,就开始调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非nil对象。否则返回nil,继续下面的动作。注意这里不要返回self,否则会形成死循环。
  3. 如果调用forwardingTargetForSelector没有效果,就开始调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil;传给一个NSInvocation并传给forwardInvocation:。
  4. 将第三步获取到的方法签名包装成Invocation传入,调用forwardInvocation:方法,如何处理就在这里面了,并返回非nil。
  5. 调用doesNotRecognizeSelector:,默认的实现是抛出异常。如果第三步没能获得一个方法签名,执行该步骤 。

3.2 oc转发源代码描述:
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
使用instrumentObjcMessageSends的方法来查看app的log信息,具体使用方法:在appdelegate.m里面定义如下:

#import "AppDelegate.h"
#import <objc/runtime.h>

void instrumentObjcMessageSends();
@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
instrumentObjcMessageSends(YES);
return YES;
}


// 下面是消息转发的log信息
+ AClass NSObject initialize
+ AClass NSObject alloc
- AClass AClass init
- NSObject NSObject init
- AClass NSObject performSelector:
+ AClass NSObject resolveInstanceMethod:
+ AClass NSObject resolveInstanceMethod:
- AClass NSObject forwardingTargetForSelector:
- AClass NSObject forwardingTargetForSelector:
- AClass NSObject methodSignatureForSelector:
- AClass NSObject methodSignatureForSelector:
- AClass NSObject class
- AClass NSObject doesNotRecognizeSelector:
- AClass NSObject doesNotRecognizeSelector:
- AClass NSObject class

第四部分 respondsToSelector、instancesRespondToSelector

1
2
3
4
5
// add1方法是不存在的,现在试试转发,看第四部分
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
AClass *acClass = [[AClass alloc] init];
[acClass respondsToSelector:@selector(add1)];
}

4.1 文字分析:
  1. 如果没有找到imp方法,就尝试一次resolveInstanceMethod,不会进行消息转发
  2. 不会进行消息转发,所以就算找不到方法,也不会报doesNotRecognizeSelector这个找不到方法的报错了
  3. 主要的原因,看respondsToSelector的源码就知道,在下面

4.2 源码分析:
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
+ (BOOL)respondsToSelector:(SEL)sel {
if (!sel) return NO;
return class_respondsToSelector_inst(object_getClass(self), sel, self);
}

- (BOOL)respondsToSelector:(SEL)sel {
if (!sel) return NO;
return class_respondsToSelector_inst([self class], sel, self);
}

---------------------------------------------
+ (BOOL)instancesRespondToSelector:(SEL)sel {
if (!sel) return NO;
return class_respondsToSelector(self, sel);
}

---------------------------------------------
BOOL class_respondsToSelector(Class cls, SEL sel)
{
return class_respondsToSelector_inst(cls, sel, nil);
}

bool class_respondsToSelector_inst(Class cls, SEL sel, id inst)
{
IMP imp;

if (!sel || !cls) return NO;

// Avoids +initialize because it historically did so.
// We're not returning a callable IMP anyway.
// 这里的resolver传入的是YES,所以当找不到方法的时候,就只会执行一次resolveInstanceMethod方法,避开了消息转发,具体看最上面的lookUpImpOrNil方法
imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, YES/*resolver*/);
return bool(imp);
}

---------------------------------------------

// respondsToSelector消息转发部分的log
+ AClass NSObject initialize
+ AClass NSObject alloc
- AClass AClass init
- NSObject NSObject init
- AClass NSObject respondsToSelector:
- AClass NSObject class
+ AClass NSObject resolveInstanceMethod:
+ AClass NSObject resolveInstanceMethod:
- AClass NSObject dealloc

第五部分 methodForSelector、instanceMethodForSelector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// add1方法是不存在的,现在试试转发,看第四部分
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
AClass *acClass = [[AClass alloc] init];
[acClass methodForSelector:@selector(add1)];
}
---------------------------------------------

+ (IMP)methodForSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return object_getMethodImplementation((id)self, sel);
}

- (IMP)methodForSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return object_getMethodImplementation(self, sel);
}

---------------------------------------------
+ (IMP)instanceMethodForSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return class_getMethodImplementation(self, sel);
}

5.1 看源码可以知道,是和respondsToSelector一样的实现机制:没有消息转发
1
2
3
4
5
6
7
8
+ AClass NSObject initialize
+ AClass NSObject alloc
- AClass AClass init
- NSObject NSObject init
- AClass NSObject methodForSelector:
+ AClass NSObject resolveInstanceMethod:
+ AClass NSObject resolveInstanceMethod:
- AClass NSObject dealloc

write: by coderiding \ by erliucxy \ 东区-中山-广东 2018.2.12 5:25:51