iOS 底层 day13 runtime 消息发送 动态解析 消
2020-09-05 本文已影响0人
望穿秋水小作坊
objc_msgSend
的执行流程可以分为三大阶段:①消息发送 ②方法动态解析 ③消息转发
一、objc_msgSend 源码最重要的方法图解
1. lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
- 这是我们
最核心
的函数的
二、objc_msgSend 消息发送
objc_msgSend 消息发送(第一阶段)1.思考 [nil test]
调用会报错吗?
- 不会
- 源码解析中
receiver 为nil
会直接退出,并不会有任何操作
三、objc_msgSend 动态方法解析
objc_msgSend 动态方法解析- 动态解析具体代码实现如下:
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
-(void)noImplementMethod;
@end
// Person.m
#import "Person.h"
#import <objc/runtime.h>
@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
有什么作用? 如何动态添加get
和setter
方法?
-
@dynamic
是告诉编译器,不要为属性自动生成get
和setter
方法 - 我们可以在
消息动态解析阶段
和消息转发阶段
为属性自定义生成get
和setter
方法
六、消息机制 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
- 如果
person
是nil
,则会进入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*/);
- 这是我们
最核心
的函数的