什么是objc_msgSend我我们感觉既熟悉又陌生,我们一步一步探索他
首先objc_msgSend这个是怎么来的
先定义一个LGPerson类
然后main.m 里面实现一下
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@property (nonatomic, copy) NSString *name;
- (void)sayHello;
- (void)sayNB;
@endNS_ASSUME_NONNULL_ENDint main(int argc, const char * argv[]) { @autoreleasepool { LGPerson *person = [[LGPerson alloc] init];; [person sayNB]; } return 0;}这里定义了一个person 的对象,实现了[person sayNB];对象方法
利用 cland -rewrite-objc main.m 看一下底层源码实现
可以看到 对象方法的实现是由((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB")); 这句代码实现,惊不惊喜意不意外
objc_msgSend是用汇编写的
汇编的优势:1:C语音中不可能通过写一个函数来保留未知的参数并且跳转到一个任意的函数指针。C语言没有满足做这件事情的必要特性
2,Objc_msgSend汇编更接近底层,效率更高
通过这段代码可以看到方法的实现是通过objc_msgSend 关联对象以及内部的方法名来具体的实现
int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB")); } return 0;}现在知道objc_msgSend的由来,我们通过底层汇编来分析一下objc_msgSend的内部的具体流程实现
arm64里面有31位寄存器,这31位中每一个都是64位,其中 x0-x7为其存放参数
cmd p0;为第一个参数,判断self接受者是否存在,如果为空则代表接受者不存在 LReturnZero,
如果存在则标记x0关联isa,isa的存在价值是可以通过isa知道元类,获取元类里面的方法

GetClassFromIsa_p16 //p16 = class ; 这句代码是为了 让isa & mask 来获取真正的Class 目的就是取面具
通过isa找到接受者之后,来读取他的方法
CacheLookUp 读取方法
方法的读取方法有三种:
NORMAL ;一般的方法差早
GETIMP ;快速直接得到IMP
LOOKUP ;慢速遍历查找
详述一下CacheLookup cache中存在的情况
ldp p10, p11, [x16, #CACHE] ;通过地址偏移16位找到cache
#if !__LP64__
and w11, w11, 0xffff // p11 = mask ;-->这个是 拿到32位mask 与_cmd进行与运算
and w12, w1, w11 ;-->w1 & w11来得到 w12 (当前的hash下标)
add p12, p10, p12, LSL #(1+PTRSHIFT) ;->通过地址拿到当前的buckets
ldp p17, p9, [x12] ; ->拿到buckets内部相应的 imp sel
cmp p9, p1 ;->判断是否对应_cmd 如果对应说明 cache中存在 则直接返回内存中的 imp

如果Cache中找不到的话 则
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 // loop3: // 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一块拼一下里面的CheckMiss ,cache中不存在的情况 CacheLookup NORMAL
__objc_msgSend_uncached
.macro CheckMiss // miss if bucket->sel == 0.if $0 == GETIMP cbz p9, LGetImpMiss.elseif $0 == NORMAL cbz p9, __objc_msgSend_uncached.elseif $0 == LOOKUP cbz p9, __objc_msgLookup_uncached
细品 __objc_msgSend_uncached 则重点是MethodTableLookup(里面貌似也就这么一句重要的)
.endmacro
STATIC_ENTRY __objc_msgSend_uncached UNWIND __objc_msgSend_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band p16 is the class to search
MethodTableLookup
TailCallFunctionPointer x17 END_ENTRY __objc_msgSend_uncached
那么接着品 MethodTableLookup
.macro MethodTableLookup // push frame SignLR stp fp, lr, [sp, #-16]! mov fp, sp // save parameter registers: x0..x8, q0..q7 sub sp, sp, #(10*8 + 8*16) stp q0, q1, [sp, #(0*16)] stp q2, q3, [sp, #(2*16)] stp q4, q5, [sp, #(4*16)] stp q6, q7, [sp, #(6*16)] stp x0, x1, [sp, #(8*16+0*8)] stp x2, x3, [sp, #(8*16+2*8)] stp x4, x5, [sp, #(8*16+4*8)] stp x6, x7, [sp, #(8*16+6*8)] str x8, [sp, #(8*16+8*8)] // receiver and selector already in x0 and x1 mov x2, x16
bl __class_lookupMethodAndLoadCache3 // IMP in x0 mov x17, x0一系列的内存偏移查找在内存中寻找方法(class_data_bits_t-> class_rw_t -> class_ro_t -> method_list_t) 通过捕捉 得到__class_lookupMethodAndLoadCache3
继续品__class_lookupMethodAndLoadCache3
id objc_msgSend_uncached(id receiver, SEL op, struct objc_class *cls){ IMP imp = _class_lookupMethodAndLoadCache3(receiver, op, cls); return imp(receiver, op, ....);}找到对应方法并缓存到cache 中,然后返回IMP
汇编方法从查找戛然而止,不过楞感觉缺点什么,欢迎路过高手,请多多指教