objc_msgSend执行流程

  • OC中的方法调用,其实都是转换为objc_msgSend函数的调用
  • objc_msgSend的执行流程可以分为3大阶段
    • 消息发送
    • 动态方法解析
    • 消息转发

源码跟读

objc-msg-arm64.s

  • ENTRY _objc_msgSend
  • b.le LNilOrTagged
  • CacheLookup NORMAL
  • .macro CacheLookup
  • .macro CheckMiss
  • STATIC_ENTRY __objc_msgSend_uncached
  • .macro MethodTableLookup
  • __class_lookupMethodAndLoadCache3

objc-runtime-new.mm

  • _class_lookupMethodAndLoadCache3
  • lookUpImpOrForward
  • getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
  • cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
  • _class_resolveInstanceMethod
  • _objc_msgForward_impcache

objc-msg-arm64.s

  • STATIC_ENTRY __objc_msgForward_impcache
  • ENTRY __objc_msgForward

Core Foundation

  • __forwarding__(不开源)

消息发送 - objc_msgSend执行流程1

  • receiver是否为nil
    • 是,退出
      • 从receiverClass的cache中查找方法
        • 找到方法 -> 调用方法结束查找
        • 没有找到方法
          • 从receiverClass的class_rw_t中查找方法
            • 找到方法 -> 调用方法结束查找,并将方法缓存到receiverClass的cache中
            • 未找到方法
              • 从superClass的cache中查找方法
                • 找到方法 -> 调用方法结束查找,并将方法缓存到receiverClass的cache中
                • 未找到方法
                  • superClass的cache中查找方法
                    • 找到方法 -> 调用方法结束查找,并将方法缓存到receiverClass的cache中
                    • 未找到方法
                      • 上层是否还有superClass
                        • 是,继续 从superClass的cache中查找方法
                        • 否,进入 动态方法解析 阶段
  • 如果是从class_rw_t中查找方法
    • 已经排序的,二分查找
    • 没有排序的,遍历查找
  • receiver通过isa指针找到receiverClass
  • receiverClass通过superClass指针找到superClass

动态方法解析 - objc_msgSend执行流程2

  • 是否曾经有动态解析
    • 是,进入 消息转发 阶段
      • 调用+resolveInstanceMethod:或者+resolveClassMethod:方法来动态解析方法
      • 标记为已经动态解析
      • 消息发送
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void other(id self, SEL _cmd) {
NSLog(@"%@ - %s - %@", self, sel_getName(_cmd), __func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
Method method = class_getInstanceMethod(self, @selector(other));
class_addMethod(self,
sel,
method_getImplementation(method),
method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
}

开发者可以实现以下方法,来动态添加方法实现

  • +resolveInstanceMethod:
  • +resolveClassMethod:

动态解析过后,会重新走“消息发送”的流程
“从receiverClass的cache中查找方法”这一步开始执行

消息转发 - objc_msgSend执行流程3

  • 调用 forwardingTargetForSelector: 方法
    • 返回值不为nil -> objc_msgSend(返回值, SEL)
    • 返回值为nil
      • 调用 methodSignatureForSelector: 方法
        • 返回值不为nil 调用 forwardInvocation: 方法
        • 返回值为nil
          • 调用doesNotRecognizedSelector:方法(常见的方法找不到错误)

示例:

1
2
3
4
5
6
7
8
9
10
- (id)forwardingTargetForSelector:(SEL)aSelector {
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog("可以作任何处理...")
}

开发者可以在forwardInvocation:方法中自定义任何逻辑
且:以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)

补充:
@dynamic是告诉编译器不用自动生成getter和setter的实现,等到运行时再添加方法实现

Super的本质

  • super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数
    • struct objc_super2
    • SEL

结构体如下:

  • receiver是消息接收者
  • current_class是recevier的class对象
1
2
3
4
struct objc_super2 {
id receiver;
Class current_class;
}

LLVM的中间代码(IR)

  • Objective-C在变为机器代码之前,会被LLVM编译器转换为中间代码(Intermediate Representation)

可以使用以下命令行指令生成中间代码

  • clang -emit-llvm -S main.m

语法简介
@ - 全局变量
% - 局部变量
alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
i32 - 32位4字节的整数
align - 对齐
load - 读出,store 写入
icmp - 两个整数值比较,返回布尔值
br - 选择分支,根据条件来转向label,不根据条件跳转的话类似 goto
label - 代码标签
call - 调用函数

参考