OC中的调用方法都是转换为objc_msgSend函数调用,调用objc_msgSend函数的方式是:
objc_msgSend(消息接受者receiver, 方法名SEL, 参数params(可不传也可多个参数))
了解了objc_msgSend调用与传参后,我们来看看苹果是如何实现objc_msgSend函数的,objc_msgSend函数实现为汇编语言,他在objc-msg-arm64.s文件中
// _objc_msgSend的开始
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
// 检查消息接受者是否为nil
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS //支持tagged point
// 小于等于0 跳转到 LNilOrTagged
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
// 等于0 跳转到LReturnZero
b.eq LReturnZero
#endif
// 将isa地址存入寄存器 p13
ldr p13, [x0] // p13 = isa
// 调用宏GetClassFromIsa_p16,传入 isa, 1 , 消息接受者地址
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
// 调用CacheLookup,并传入 NORMAL, _objc_msgSend, __objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
// 消息接受者为nil,跳转到LReturnZero
b.eq LReturnZero // nil check
// 得到tagged point 的class
GetTaggedClass
// 调用LGetIsaDone
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
// 返回 nil
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
// _objc_msgSend 的开始
END_ENTRY _objc_msgSend
ENTRY _objc_msgSend:进入objc_msgSend函数
cmp p0, #0:这里p0是传入的第一个参数 - 消息接受者,cmp比较,用p0和0做比较
根据是否为真机进行 判断p0的值,然后进行跳转
#if SUPPORT_TAGGED_POINTERS // 真机
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else // 其他
b.eq LReturnZero
#endif
ldr p13, [x0]:将isa存如寄存器p13中,[x0]是消息接受者的内存中的首地址,也就是isa。
GetClassFromIsa_p16 p13, 1, x0:
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA
// 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
.else
// 64-bit packed isa
ExtractISA p16, \\src, \\auth_address
.endif
#else
// 32-bit raw isa
mov p16, \\src
#endif
.endmacro
GetClassFromIsa_p16是一个宏,从上下文调用的值, src = isa, needs_auth = 1, auth_address = 消息接受者, 在iOS真机中SUPPORT_INDEXED_ISA = 0,并且 LP64 为真,所以会执行ExtractISA p16, \src, \auth_address 命令,ExtractISA在__has_feature(ptrauth_calls)和非__has_feature(ptrauth_calls)的实现是不同的
and是位运算中&操作
__has_feature(ptrauth_calls)
and $0, $1, #ISA_MASK:这里可以看做p16 = isa & ISA_MASK
.macro ExtractISA
and $0, $1, #ISA_MASK
#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP
//直接去掉认证信息。硬件级别的。
xpacd $0
#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
mov x10, $2
// x10 #0x6AE1 << 48 movk: Move 16-bit immediate into register, keeping other bits unchanged.
movk x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
//autda In the general-purpose register or stack pointer that is specified by <Xn|SP> for AUTDA.
autda $0, x10
#endif
.endmacro
非__has_feature(ptrauth_calls):这里可以看做p16 = isa & ISA_MASK
.macro ExtractISA
and $0, $1, #ISA_MASK
.endmacro
LGetIsaDone:
计算出isa和class后,调用CacheLookup,看看有咩有缓存
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
Tips:如果学过cache_t::insert函数实现逻辑,那么理解CacheLookup会更加容易。
展示的源码去掉了非iOS真机的部分,只保留了iOS arm64的代码,CacheLookup可以分为三个部分来讲,