Runtime 03 - objc_msgSend、super
Objective-C 的消息机制
- Objective-C 中的方法调用其实都是被编译器转换成了对 objc_msgSend 函数的调用,给 receiver(方法调用者)发送了一条消息(selector:方法名)。
- objc_msgSend 函数底层有 3 大阶段:
- 消息发送(当前类、父类中查找)。
- 动态方法解析。
- 消息转发。
> objc_msgSend 源码解读顺序
> 1. objc-msg-arm64.s
> - ENTRY _objc_msgSend // _objc_msgSend 函数入口
> - b.le LNilOrTagged // 判断 receiver 是否为 nil
> - b.eq LReturnZero // 如果 receiver 为 nil,流程结束
> - CacheLookup NORMAL // 从缓存中查找方法,进入 CacheLookup
> - .macro CacheLookup
> - CacheHit $0 // 找到了方法,进入 CacheHit
> - CheckMiss $0 // 没有找到方法,进入 CheckMiss
> - .macro CacheHit
> - .if $0 == NORMAL // 如果参数为 NORMAL,直接调用
> - MESSENGER_END_FAST
> - br x17 // 调用方法
> - .macro CheckMiss
> - .elseif $0 == NORMAL // 调用 __objc_msgSend_uncached
> - cbz x9, __objc_msgSend_uncached
> - STATIC_ENTRY __objc_msgSend_uncached
> - MethodTableLookUp // 从方法列表中查找
> - br x17 // 调用方法
> - .macro MethodTableLookup
> - bl __class_lookupMethodAndLoadCache3 // 调用 C++ 函数
> - mov x17, x0 // 将方法返回 __objc_msgSend_uncached
> 2. objc-runtime-new.mm
> - _class_lookupMethodAndLoadCache3
> - return lookUpImpOrForward
> - lookUpImpOrForward
> - cache_getImp // 从 reveiverClass 的缓存中查找
> - return imp // 找到方法后返回 IMP
> - getMethodNoSuper_nolock // 从 reveiverClass 的方法列表中查找
> - search_method_list // 从方法列表中查找
> - log_and_fill_cache // 找到方法进行缓存
> - return imp // 返回 IMP
> - for (Class curClass = cls->superclass;
> curClass != nil;
> curClass = curClass->superclass) // 从 superClass 中查找
> - cache_getImp // 从 superClass 的缓存中查找
> - log_and_fill_cache // 找到方法缓存缓存到 reveiverClass
> - return imp // 返回 IMP
> - getMethodNoSuper_nolock // 从 superClass 的方法列表中查找
> - search_method_list // 从方法列表中查找
> - log_and_fill_cache // 找到方法缓存缓存到 reveiverClass
> - return imp // 返回 IMP
> - _class_resolveInstanceMethod // 没有找到方法,进行动态方法解析
> - if (!cls->isMetaClass())
> - _class_resolveInstanceMethod // 动态解析实例方法
> - else
> - _class_resolveClassMethod // 动态解析类方法
> - if (!lookUpImpOrNil()) // 类方法解析失败会尝试解析实例方法
> - _class_resolveInstanceMethod
> - return (IMP)_objc_msgForward_impcache // 动态方法解析失败,尝试消息转发
>
> 3. objc-msg-arm64.s
> - STATIC_ENTRY __objc_msgForward_impcache // 从 C++ 函数调用到这里
> - ENTRY __objc_msgForward // 尝试消息转发
>
> 4. Core Foundation
> - `__forwarding__` (不开源)
objc_msgSend 01 - 消息发送
-
objc_msgSend:
- 函数签名:
objc_msgSend(void /* id self, SEL op, ... */)
- 我们在 Objective-C 中的方法调用在编译阶段会被转换为对 objc_msgSend 函数的调用。
<br />
例如我们编写一段下面的代码:
[instance instanceFunc];
在编译阶段会被转换为:
objc_msgSend(instance, sel_registerName("instanceFunc"));
- 函数签名:
如果是从 class_rw_t 中查找方法:
- 已经排序的,二分查找。
- 没有排序的,遍历查找。
receiver 通过 isa 指针找到 receiverClass。
receiverClass 通过 superclass 指针找到 superClass。
objc_msgSend 02 - 动态方法解析
开发者通过可以实现以下方法,来动态添加方法实现:
- +resolveInstanceMethod:
- +resolveClassMethod:
动态解析过后,会重新走 “消息发送” 的流程
- 从 “在 receiverClass 的 cache 中查找方法“ 这一步开始执行。
动态添加方法示例:
objc_msgSend 03 - 消息转发
开发者可以在 forwardInvocation: 方法中自定义任何逻辑。
以上方法都有对象方法、类方法两个版本。
生成 NSMethodSignature
NSMethodSignature *signature = [NSMethodSignature signatureWithObjeCTypes:"i@:ii"];
MyClass *instance = [[MyClass alloc] init];
SEL sel = @selector(myFunc:param2:)
NSMethodSignature *signature = [instance methodSignatureForSelector:sel];
super
- 用 clang -rewrite-objc 指令转换后的 C++ 代码看,super 调用,底层会转换为对 objc_msgSendSuper 函数的调用。
- objc_msgSendSuper 函数接收两个参数:
-
objc_super2:
struct __rw_objc_super { struct objc_object *object; // 消息接受者 struct objc_object *superClass; // receiver 的 superclass };
-
SEL:
消息(要调用的方法)。
-
- objc_msgSendSuper 函数接收两个参数:
- 通过 LLVM 中间代码、打断点或 objc 源码看,底层是对 objc_msgSendSuper2 汇编函数的调用。
- objc_msgSendSuper2 函数接收两个参数:
-
objc_super2:
struct objc_super2 { id object; // 消息接受者 Class current_class; // receiver 的 Class 对象,通过 current_class->supperclass 找到父类的 Class 对象 };
-
SEL:
消息(要调用的方法)。
-
- objc_msgSendSuper2 函数接收两个参数:
转换后的 C++ 代码作为参考,实际在底层调用的是 objc_msgSendSuper2 函数。
LLVM 中间代码
- Objective-C 被转换为机器码之前,会被 LLVM 编译器转换为中间代码(Intermediate Representation)。
- 生成 LLVM 中间代码的指令为:
clang -emit-llvm -S source.m
- 语法简介(官方文档):
- @ - 全局变量
- % - 局部变量
- alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
- i32 - 32 位 4 字节的整数
- align - 对齐
- load - 读出,store - 写入
- icmp - 两个整数值比较,返回布尔值
- br - 选择分支,根据条件来转向 label,不根据条件跳转的话类似 goto
- label - 代码标签
- call - 调用函数
网友评论