问题
- OC 方法的调用本质是什么
- OC 函数调用是什么样子的?
- 方法发送的方式有哪几种?
- 方法的快速查找流程?
OC 方法调用的本质是什么
-
我们新建一个项目,创建一个LBPerson , 然后我们在main 函数里面进行方法的调用:
int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... LBPerson *person = [[LBPerson alloc] init]; [person sayHello]; } return 0; } -
我们Clang一下:
clang -rewrite-objc main.m -o main.cppint main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; LBPerson *person = ((LBPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LBPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LBPerson"), sel_registerName("alloc")), sel_registerName("init")); ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello")); } return 0; } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 }; -
看起来有点麻烦我们去掉不必要的强转:
int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; LBPerson *person = objc_msgSend((id)objc_getClass("LBPerson"), sel_registerName("alloc")), sel_registerName("init"); objc_msgSend((id)person, sel_registerName("sayHello")); } return 0; } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 }; -
我们可以看出本质是调用了
objc_msgSend函数,我们可以联想到方法里面有两个默认的参数,一个是id self,还有一个是sel _cmd。这里应该就呼应上了,大概就清楚是怎么调用方法的了。那么objc_msg 到底做了什么呢?还有一个问题函数的调用本地是什么样子的呢?我们修改步骤1 的代码如下:void testFunc() { printf("hello"); } int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... LBPerson *person = [[LBPerson alloc] init]; [person sayHello]; testFunc(); } return 0; }接着 我们继续clang一下:

借此我们也可以看出方法 和 函数的区别了。我们清晰的看到函数并没有做什么特别操作。
那么方法的调用有多少种呢?
我们在探究前,有个东西需要关闭一下,否则可能会出现如下问题:

我们只要关闭这个东西

进入正题
调用方法:
LBPerson *person = [[LBPerson alloc] init];
LBStudent *student = [[LBStudent alloc] init];
[person sayHello];
// 给对象发送消息
objc_msgSend(person,sel_registerName("sayHello"));
objc_msgSend(person,@selector(sayHello));
// 给类发送消息
objc_msgSend(objc_getClass("LBPerson"), @selector(sayHaHa));
// 给父类发送消息
struct objc_super LBSuper;
LBSuper.receiver = student;
LBSuper.super_class = [LBPerson class];
objc_msgSendSuper(&LBSuper, @selector(sayHello));
struct objc_super LBSuper2;
LBSuper2.receiver = student;
LBSuper2.super_class = class_getSuperclass(object_getClass(student));
objc_msgSendSuper(&LBSuper2, @selector(sayHello));
打印方法:
2020-01-19 15:05:45.135074+0800 msgSendDemo[69339:82747636] func ---- -[LBPerson sayHello] ---
2020-01-19 15:05:45.135458+0800 msgSendDemo[69339:82747636] func ---- -[LBPerson sayHello] ---
2020-01-19 15:05:45.135489+0800 msgSendDemo[69339:82747636] func ---- -[LBPerson sayHello] ---
2020-01-19 15:05:45.135512+0800 msgSendDemo[69339:82747636] -----+[LBPerson sayHaHa] ---
2020-01-19 15:05:45.135533+0800 msgSendDemo[69339:82747636] func ---- -[LBPerson sayHello] ---
2020-01-19 15:05:45.135559+0800 msgSendDemo[69339:82747636] func ---- -[LBPerson sayHello] ---
方法的调用流程
我们知道了方法的本质是objc_msdSend 那么我们就来跟一下

紧接着我们跟进去

这个时候我们跟进去了 接着我们发现跟不进去了。这个时候我们跟一下苹果官方开源的objc750 源码,我们在汇编代码里面找到了这么一句话。

紧接着我们往下读:
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
// 拿到我门上面的对象的isa。
ldr p13, [x0] // p13 = isa
//为什么到p16
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
// 进行缓存的搜所
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
我们来看一下:GetClassFromIsa_p16
下面的代码里 为什么平移16 位? Isa + superClass 是16位。
.macro GetClassFromIsa_p16 /* src */
#if SUPPORT_INDEXED_ISA
// Indexed isa
mov p16, $0 // 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__
// 64-bit packed isa
// 取面具,蒙版
and p16, $0, #ISA_MASK
#else
// 32-bit raw isa
mov p16, $0
#endif
.endmacro
我们获得isa 之后,我们就进行方法的查找
.macro CacheLookup
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
and w11, w11, 0xffff // p11 = mask
#endif
// 取方法下标
and w12, w1, w11 // x12 = _cmd & mask
add p12, p10, p12, LSL #(1+PTRSHIFT) ,移动相应的位置得到相对正确的地址
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more 走流程2
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f 走流程3
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask ,进行缓存一下
add p12, p12, w11, UXTW #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // double wrap
JumpMiss $0 // 没有就走jumpMiss
.endmacro