方法的本质
方法的本质是objc_msgSend(消息发送),而objc_msgSend调用需要消息的接收者和sel+参数
@interface ZMPerson : NSObject
- (void)sayNB;
- (void)sayGood:(NSString *)job;
@end
@implementation ZMPerson
- (void)sayNB {
NSLog(@"%s", __func__);
}
// 调用:
ZMPerson *person = [ZMPerson alloc];
[person sayNB];
[person sayGood: @"coder"];
// C++
ZMPerson *person = ((ZMPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ZMPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("sayGood:"), (NSString *)&__NSConstantStringImpl__var_folders_7t_93mn_rbs0p1d7bkzy4z_f0s40000gn_T_main_e9af44_mi_1);
@end
- 可以通过
objc_msgSend直接调用方法- 导入头文件
#import <objc/message.h> - 关闭
objc_msgSend的检查机制:target->Build Setting->搜索msg->Enable strict Checking of obc_msgSend Calls改为NO。 - 模仿调用
objc_msgSend(person, sel_registerName("sayNB")); - 结果等价
- 导入头文件
- 调用父类方法
- 编译为c++,多了个
objc_msgSendSuper:
- 编译为c++,多了个
static void _I_WSTeacher_sayGood(WSTeacher * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("WSTeacher"))}, sel_registerName("sayGood"));
}
- 源码查看
objc_msgSendSuper
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
// `objc_msgSendSuper`有两个参数,第二个是`SEL`,第一个是个`objc_super`结构体,结构如下:
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver; //接受者
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class; // 现在用的是这个
#endif
/* super_class is the first class to search 是第一次查找的类 */
};
objc_msgSend汇编
- 在
objc-msg-arm64.s汇编文件中找到入口
SUPPORT_TAGGED_POINTERS
它是小对象模式,它的定义是:
#if __arm64__
...
#define SUPPORT_TAGGED_POINTERS 1
...
// true arm64
#else
// arm64_32
#define SUPPORT_TAGGED_POINTERS 0
...
#endif
如果是模拟器,SUPPORT_TAGGED_POINTERS为0,`b.eq LReturnZero`:这个是`p0`和`0`相等,也就是消息接受者不存在的意思,这时候返回`LReturnZero`:也就是 直接返回nil
GetClassFromIsa_p16
// src=p13=isa, needs_auth=1, auth_address=x0
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA //arm64
// Indexed isa
mov p16, \src // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
mov p16, \src //将src赋值给p16
.else
// 64-bit packed isa
ExtractISA p16, \src, \auth_address //得到p16
.endif
#else
// 32-bit raw isa
mov p16, \src
#endif
.endmacro
GetClassFromIsa_p16是一个宏定义,要传入三个参数,由于SUPPORT_INDEXED_ISA在arm64中为1,则直接跳过看__LP64__,这里传入的参数needs_auth=1,所以看ExtractISA这个方法:
ExtractISA
在 arm64-asm.h文件中 看到这个方法
.macro ExtractISA
and $0, $1, #ISA_MASK
.endmacro
ExtractISA也是个宏定义,and是&运算,这里是将$1 & ISA_MASK,再将得到的结果赋值给$0,而$0就是p16,所以这是根据isa与ISA_MASK拿到class的过程,然后回到最开始GetClassFromIsa_p16 p13, 1, x0,这个步骤就是先拿到class,所以这一行的注释上p16 = class
CacheLookup
函数CacheLookup,并传入三个参数NORMAL, _objc_msgSend, __objc_msgSend_uncached,我们进入方法再进行分析:
获取bucket和查找index
几个主要的步骤
-
- 获取
cache_t:ldr p11, [x16, #CACHE],这里x16是p16地址,我们知道p16是class,而#CACHE是16字节,所以此处是:class地址平移16个字节,刚好取到cache_t。
- 获取
-
- 获取
buckets:#0x0000fffffffffffe是maskShift,p11 & maskShift得到buckets,赋值给p10。
- 获取
-
- 获取第一次查找的
index:p1是_cmd,LSR是>>,eor是^(异或)。首先p12 = p1 ^ (p1 >> 7),再p12 = p12 & mask就得到了要查找的下标index。
- 获取第一次查找的
查找缓存
首先查找 buckets首地址到 index 之间
- 先获取到要查找的
bucket,这里获取bucket的方式是内存平移,相当于b[i]一样,然后我们进行遍历查找。 ldp是同时操作两个寄存器,首先将buckets地址左移一个bucket单位,然后得到的imp和sel分别赋值给p17和p9。p1是要存的sel,当和p9不同时,走步骤3,当sel不存在时,走MissLabelDynamic,当第一个查找的bucket>buckets首地址时,buckets地址再左移一个bucket单位继续对比。- 当
p1 = p9,则缓存命中,走CacheHit。
- 这里是对
imp进行解码,然后直接返回imp。 - 查找过程如下:
- 如何这个判断里没有找到,将继续在下面判断查找
再从最后一个bucket往index遍历查找
- 三步走
-
- 获取最后一个
bucket
- 获取最后一个
-
- 获取
index处的bucket
- 获取
-
- 从最后一个
bucket往index处的bucket遍历。
- 从最后一个
-
- 图解
- 如果执行完,还是没有找到,就会走
__objc_msgSend_uncached方法:
整体流程图
汇编相关符号
| 汇编符号 | |
|---|---|
| cmd | 比较 |
| b.le | 小于 |
| b.eq | 相等 |
| ldr将指令读到目标寄存器 | |
| mov将数据存到寄存器 | |
| and&(按位与) | |
| tbnz不为0跳转 | |
| LSR>>(右移) | |
| eor^(按位异或) | |
| LSL<<(左移) | |
| ldp操作两个寄存器 | |
| b.ne不相等 | |
| cbz是否存在,不存在跳转 | |
| br跳转指令 | |
| b.hs大于等于 | |
| b.hi大于 | |
| ccmp条件比较 |