关注仓库,及时获得更新:iOS-Source-Code-Analyze
因为 ObjC 的 runtime 只能在 Mac OS 下才能编译,所以文章中的代码都是在 Mac OS,也就是
x86_64
架构下运行的,对于在 arm64 中运行的代码会特别说明。
写在前面
如果你点开这篇文章,相信你对 Objective-C 比较熟悉,并且有多年使用 Objective-C 编程的经验,这篇文章会假设你知道:
- 在 Objective-C 中的“方法调用”其实应该叫做消息传递
[receiver message]
会被翻译为objc_msgSend(receiver, @selector(message))
- 在消息的响应链中可能会调用
- resolveInstanceMethod:
或者- forwardInvocation:
等方法 - 关于选择子 SEL 的知识
如果对于上述的知识不够了解,可以看一下这篇文章 Objective-C Runtime,但是其中关于
objc_class
的结构体的代码已经过时了,不过不影响阅读以及理解。 - 方法在内存中存储的位置,深入解析 ObjC 中方法的结构
文章中不会刻意区别方法和函数、消息传递和方法调用之间的区别。
- 能翻墙(会有一个 Youtube 的链接)
概述
关于 Objective-C 中的消息传递的文章真的是太多了,而这篇文章又与其它文章有什么不同呢?
由于这个系列的文章都是对 Objective-C 源代码的分析,所以会从 Objective-C 源代码中分析并合理地推测一些关于消息传递的问题。
关于 @selector() 你需要知道的
因为在 Objective-C 中,所有的消息传递中的“消息“都会被转换成一个 selector
作为 objc_msgSend
函数的参数:
1 |
[object hello] -> objc_msgSend(object, @selector(hello)) |
这里面使用 @selector(hello)
生成的选择子 SEL 是这一节中关注的重点。
我们需要预先解决的问题是:使用 @selector(hello)
生成的选择子,是否会因为类的不同而不同?各位读者可以自己思考一下。
先放出结论:使用 @selector()
生成的选择子不会因为类的不同而改变,其内存地址在编译期间就已经确定了。也就是说向不同的类发送相同的消息时,其生成的选择子是完全相同的。
1 2 3 4 |
XXObject *xx = [[XXObject alloc] init] YYObject *yy = [[YYObject alloc] init] objc_msgSend(xx, @selector(hello)) objc_msgSend(yy, @selector(hello)) |
接下来,我们开始验证这一结论的正确性,这是程序主要包含的代码:
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 |
// XXObject.h #import @interface XXObject : NSObject - (void)hello; @end // XXObject.m #import "XXObject.h" @implementation XXObject - (void)hello { NSLog(@"Hello"); } @end // main.m #import #import "XXObject.h" int main(int argc, const char * argv[]) { @autoreleasepool { XXObject *object = [[XXObject alloc] init]; [object hello]; } return 0; } |
在主函数任意位置打一个断点, 比如 -> [object hello];
这里,然后在 lldb 中输入:
这里面我们打印了两个选择子的地址@selector(hello)
以及 @selector(undefined_hello_method)
,需要注意的是:
@selector(hello)
是在编译期间就声明的选择子,而后者在编译期间并不存在,undefined_hello_method
选择子由于是在运行时生成的,所以内存地址明显比hello
大很多
如果我们修改程序的代码:
在这里,由于我们在代码中显示地写出了 @selector(undefined_hello_method)
,所以在 lldb 中再次打印这个 sel
内存地址跟之前相比有了很大的改变。
更重要的是,我没有通过指针的操作来获取 hello
选择子的内存地址,而只是通过 @selector(hello)
就可以返回一个选择子。
从上面的这些现象,可以推断出选择子有以下的特性:
- Objective-C 为我们维护了一个巨大的选择子表
- 在使用
@selector()
时会从这个选择子表中根据选择子的名字查找对应的SEL
。如果没有找到,则会生成一个SEL
并添加到表中 - 在编译期间会扫描全部的头文件和实现文件将其中的方法以及使用
@selector()
生成的选择子加入到选择子表中
在运行时初始化之前,打印 hello
选择子的的内存地址:
message.h 文件
Objective-C 中 objc_msgSend
的实现并没有开源,它只存在于 message.h
这个头文件中。
1 2 3 4 5 6 7 8 |
/** * <a href="http://www.jobbole.com/members/smartsl">@note</a> When it encounters a method call, the compiler generates a call to one of the * functions c objc_msgSend, c objc_msgSend_stret, c objc_msgSendSuper, or c objc_msgSendSuper_stret. * Messages sent to an object’s superclass (using the c super keyword) are sent using c objc_msgSendSuper; * other messages are sent using c objc_msgSend. Methods that have data structures as return values * are sent using c objc_msgSendSuper_stret and c objc_msgSend_stret. */ OBJC_EXPORT id objc_msgSend(id self, SEL op, ...) |
在这个头文件的注释中对消息发送的一系列方法解释得非常清楚:
当编译器遇到一个方法调用时,它会将方法的调用翻译成以下函数中的一个
objc_msgSend
、objc_msgSend_stret
、objc_msgSendSuper
和objc_msgSendSuper_stret
。 发送给对象的父类的消息会使用objc_msgSendSuper
有数据结构作为返回值的方法会使用objc_msgSendSuper_stret
或objc_msgSend_stret
其它的消息都是使用objc_msgSend
发送的
在这篇文章中,我们只会对消息发送的过程进行分析,而不会对上述消息发送方法的区别进行分析,默认都使用 objc_msgSend
函数。
objc_msgSend 调用栈
这一小节会以向 XXObject
的实例发送 hello
消息为例,在 Xcode 中观察整个消息发送的过程中调用栈的变化,再来看一下程序的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ayon-striped-num" data-line="crayon-5812a533b4667722682719-18">18 19 20 21 22 ܚ特别说明。
写在前面如果你点开这篇文章,相信你对 Objective-C 比较熟悉,并且有多年使用 Objective-C 编程的经验,这篇文章会假设你知道:
概述关于 Objective-C 中的消息传递的文章真的是太多了,而这篇文章又与其它文章有什么不同呢? 由于这个系列的文章都是对 Objective-C 源代码的分析,所以会从 Objective-C 源代码中分析并合理地推测一些关于消息传递的问题。 关于 @selector() 你需要知道的因为在 Objective-C 中,所有的消息传递中的“消息“都会被转换成一个
这里面使用 我们需要预先解决的问题是:使用 先放出结论:使用
接下来,我们开始验证这一结论的正确性,这是程序主要包含的代码:
在主函数任意位置打一个断点, 比如 这里面我们打印了两个选择子的地址
如果我们修改程序的代码: 在这里,由于我们在代码中显示地写出了 更重要的是,我没有通过指针的操作来获取 从上面的这些现象,可以推断出选择子有以下的特性:
在运行时初始化之前,打印 message.h 文件Objective-C 中
在这个头文件的注释中对消息发送的一系列方法解释得非常清楚:
在这篇文章中,我们只会对消息发送的过程进行分析,而不会对上述消息发送方法的区别进行分析,默认都使用 objc_msgSend 调用栈这一小节会以向
|