本系列笔记参考 Runtime
源码为 objc4-779.1
。
什么是 Runtime
Runtime
是一套使用 C、C++、汇编实现的 API,调用 ObjC 方法时,实际上是执行的 Runtime
接口,最终执行的其实是 C/C++ 代码。
Runtime
可以理解为构成 ObjC 语言的一部分。ObjC 为 C 语言加上的面向对象的特性,通过 Clang 编译器和 Runtime
实现。
编译 ObjC 文件
使用 clang -rewrite-objc Hello.m
命令。
使用 Runtime
ObjC 方法调用
ObjC 方法调用本质上就是在调用 Runtime
的 API,比如使用 ObjC 编写如下代码:
1 | Person *olivia = [[Person alloc] init]; |
使用命令 clang -rewrite-objc main.m
编译成 C++ 代码后如下:
1 | Person *olivia = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")); |
可以看到,ObjC 中调用方法本质上是在调用 objc_msgSend
函数:
1 | // message.h |
这个函数第一个参数就是方法的调用者(receiver
),第二个参数是 SEL
类型,上面代码中使用 sel_registerName("方法名")
获得,如果方法含有参数,那么就是 objc_msgSend
中第三个以及之后的参数。其中 SEL
就是 方法选择器(selector)
,我们常常使用 @selector()
获得,它实际上是一个结构体指针。
1 | // objc.h |
而 sel_registerName
就是通过方法名这个字符串去获得 SEL
:
1 | OBJC_EXPORT SEL _Nonnull |
需要注意的是,这里说的方法名包含参数个数信息,不包含参数类型和返回值类型信息,也就是说,我们可以从一个 SEL
中得到这个方法的名字以及有几个参数(如 sayHi:
表示这个方法具有一个参数),但是无法知道这个方法的返回值类型和参数类型。所以 ObjC 中如果函数名相同且参数个数相同,即使返回值类型和参数类型都不同,编译器也是会报错的。
NSObject 方法
NSObject 中的方法大多都是调用的 Runtime
的 API:
1 | - (id)performSelector:(SEL)aSelector; |
直接调用 Runtime 库函数
1 |
可以通过引入 Runtime
头文件,直接调用 Runtime
中的 API,基于 Runtime
的很多实现也是通过调用 Runtime
API 完成的。