iOS 底层 day13 runtime 消息发送 动态解析 消息转发

objc_msgSend 的执行流程可以分为三大阶段:①消息发送 ②方法动态解析 ③消息转发

一、objc_msgSend 源码最重要的方法图解

1. lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
  • 这是我们最核心的函数的
lookUpImpOrForward 核心函数图解

二、objc_msgSend 消息发送

objc_msgSend 消息发送(第一阶段)
1.思考 [nil test] 调用会报错吗?
  • 不会
  • 源码解析中 receiver 为nil 会直接退出,并不会有任何操作

三、objc_msgSend 动态方法解析

objc_msgSend 动态方法解析
  • 动态解析具体代码实现如下:
// Person.h
#import 
@interface Person : NSObject
-(void)noImplementMethod;
@end


// Person.m
#import "Person.h"
#import 
@implementation Person
void dynamicResolveMethod(id self, SEL _cmd) {
    printf("dynamicResolveMethod \n");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(noImplementMethod)) {
        class_addMethod(self, sel, (IMP)dynamicResolveMethod, "v16@0:8");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

//+ (BOOL)resolveClassMethod:(SEL)sel
@end

四、objc_msgSend 消息转发

objc_msgSend 消息转发图解
1.用消息转发流程来实现 [person run:10] 调用 [cat run:10]
  • 方法一:
// person.m
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(run:)) {
        return [[Cat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
  • 方法二:
// person.m
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(run:)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:i"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    anInvocation.target = [[Cat alloc] init];
    [anInvocation invoke];
}
2.用消息转发流程来实现 [person run:10] 调用 [cat walk:10]
// person.m
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(run:)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:i"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    anInvocation.target = [[Cat alloc] init];
    anInvocation.selector = sel_registerName("walk:");
    [anInvocation invoke];
}
3.用消息转发流程来person无论调用什么方法都不报错的机制
// person.m
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    printf("%s 找不到方法\n", sel_getName(anInvocation.selector));
}

五、一些综合性问题

1. 解释一下什么情况下会出现 unrecognize selector sent to instance?
  • 如果仅仅回答,在调用未实现的方法时会报此错误,就显得回答比较浅
  • 结合本节 消息机制三大流程,就可以回答得全面到位
2. @dynamic有什么作用? 如何动态添加getsetter方法?
  • @dynamic是告诉编译器,不要为属性自动生成 getsetter方法
  • 我们可以在消息动态解析阶段消息转发阶段为属性自定义生成 getsetter方法

六、消息机制 objc_msgSend 源码解读流程

Person *person = [[Person alloc] init];
[person personTest];
///objc_msgSend(person, @selector(personTest));
1. objc-msg-arm64.s (入口文件)
  • objc_msgSend有部分代码是汇编写的
  • 我们主要研究 arm64 所以找它
1.01. ENTRY _objc_msgSend 函数入口
1.02. b.le LNilOrTagged
  • b.le 等价于 if(x0 <= nil)
  • x0是寄存器,这个情况下代表传入的第一个参数,也就是person
  • 如果personnil,则会进入 LNilOrTagged函数,然后ret结束调用
1.03. CacheLookup NORMAL
  • 因为 person 不等于 nil ,所以函数会顺序执行
  • 会进入 CacheLookup 函数,并且传入 NORMAL 参数
1.04. .macro CacheLookup
  • 说明 CacheLookup 是一个
  • 这里会用汇编查找一个类的缓存,如果缓存命中,会进入 CacheHit $0
  • 如果缓存未命中,则进入 .macro CheckMiss
1.05. .macro CacheLookup
  • 找到 .elseif $0 == NORMAL 条件下的调用
1.06. STATIC_ENTRY __objc_msgSend_uncached
1.07. .macro MethodTableLookup
1.08. .macro MethodTableLookup
1.09. bl __class_lookupMethodAndLoadCache3
  • 进入 __class_lookupMethodAndLoadCache3 函数

  • 我们全局搜索__class_lookupMethodAndLoadCache3,发现并没有函数定义

  • 这时候我们需要补充一个知识点 __class_lookupMethodAndLoadCache3 这种汇编函数,对应的C语言函数是少一个下划线的,也就是查找 _class_lookupMethodAndLoadCache3

2. objc-runtime-new.mm (入口文件)
2.01. IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
2.02. lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
  • 这是我们最核心的函数的
lookUpImpOrForward 核心函数图解

你可能感兴趣的:(iOS 底层 day13 runtime 消息发送 动态解析 消息转发)