方法的本质
方法的本质是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条件比较 |