美文网首页
objc_msgSend解析

objc_msgSend解析

作者: linc_ | 来源:发表于2022-05-06 17:20 被阅读0次

    方法的本质

    我们通过clang命令 clang -rewrite-objc main.m 将main文件编译成main.cpp文件

    //main函数中方法的调用
    LGPerson *person = [LGPerson alloc];
    [person sayHello];
    
    //clang后的方法的调用
    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("sayHello"));
    

    可以看出 方法的调用本质是 objc_msgSend来发送消息。我们还可以手动调用来验证一下
    导入头文件<objc/message.h>

    LGPerson *person = [LGPerson alloc];
    //1.方法的调用
    [person study];
    //2.消息发送 ((void (*)(id, SEL))(void *)objc_msgSend)(p, sel_registerName("study"));
    

    打印结果如下


    image.png

    可以看到打印结果是一致的,这也证明了[person study]等价于objc_msgSend(person, sel_registerName("study"))。

    执行父类的方法

    - (instancetype)init {
        if (self = [super init]) {
            NSLog(@"%@", [self class]);
            NSLog(@"%@", [super class]);
        }
        return self;
    }
    

    先来看一下这段代码打印的是什么


    image.png

    打印的结果都是 LGTeacher,这是为什么呢?
    我们用clang命令编译LGTeacher.m,得到如下编译代码

    static instancetype _I_ LGTeacher_init(LGTeacher * self, SEL _cmd) {
        if (self = ((LGTeacher *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("init"))) {
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_x6_75ftxbbd4t97nl_2t2sh7kww0000gn_T_ LGTeacher_8d6cff_mi_0, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_x6_75ftxbbd4t97nl_2t2sh7kww0000gn_T_ LGTeacher_8d6cff_mi_1, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGStudent"))}, sel_registerName("class")));
        }
        return self;
    }
    

    [self class]发送的消息是objc_msgSend,消息接收者是self,方法编号是class
    [super class]本质是objc_msgSendSuper, 消息接收者也是self, 方法编号class, 所以[super class]打印出来的一样是self这个对象所指的类,也就是LGStudent。

    小结

    1. [super class]中的super只是一个编译指示器,就是让当前对象获取该方法,越过本类,直接从父类找。
    2. 消息的接受这还是本类self

    方法快速查找

    image.png

    搜索objc_msgSend 在源码中以 objc-msg-arm64为例,看objc_msg_arm64中的源码,可以看到都是汇编代码。这里我们可以思考一个问题,苹果为什么objc_msgSend这部分代码要使用汇编来编写呢?答案很简单--效率。汇编的效率是比c/c++更快的,因为汇编大多是直接对寄存器的读写,相比较对内存的操作更底层,效率也更高。

    //进入objc_msgSend流程
    ENTRY _objc_msgSend
    //流程开始,无需frame
    UNWIND _objc_msgSend, NoFrame
    //判断p0(消息接受者)是否存在,不存在则重新开始执行objc_msgSend
    cmp p0, #0 // nil check and tagged pointer check
    //如果支持小对象类型。返回小对象或空
    #if SUPPORT_TAGGED_POINTERS
    //b是进行跳转,b.le是小于判断,也就是小于的时候LNilOrTagged
    b.le LNilOrTagged //  (MSB tagged pointer looks negative)
    #else
    //等于,如果不支持小对象,就LReturnZero
    b.eq LReturnZero
    #endif
    //通过p13取isa
    ldr p13, [x0] // p13 = isa
    //通过isa取class并保存到p16寄存器中
    GetClassFromIsa_p16 p13, 1, x0 // p16 = class
    //LGetIsaDone是一个入口
    LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    //进入到缓存查找或者没有缓存查找方法的流程
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
    #if SUPPORT_TAGGED_POINTERS
    LNilOrTagged:
    // nil check判空处理,直接退出
    b.eq LReturnZero // nil check
    GetTaggedClass
    b LGetIsaDone
    // SUPPORT_TAGGED_POINTERS
    #endif
    LReturnZero:
    // x0 is already zero
    mov x1, #0
    movi d0, #0
    movi d1, #0
    movi d2, #0
    movi d3, #0
    ret
    END_ENTRY _objc_msgSend
    

    objc_msgSend 开始到找类对像cache方法结束的流程。
    首先判断receiver是否存在,以及是否是taggedPointer类型的指针,如果不是我们取出对象的isa指针(x13寄存器中),通过isa指针找到类对象(x16寄存器),然后通过CacheLookup,在类对象的cache中查找是否有方法缓存,如果有就调用,如果没有走objc_msg_uncached分支。

    方法慢速查找(从方法列表中查找)

    方法在cache内找不到就是调用_objc_msgSend_uncached函数,我们在_objc_msgSend_uncached函数内部可以看到它会调用MethodTableLookup函数,MethodTableLookup函数会调用_lookUpImpOrForward。_lookUpImpOrForward函数在汇编内是看不到,那我们直接在源码内搜索lookUpImpOrForward。

    image.png image.png

    lookUpImpOrForward函数内部首先会判断cache内部有没有方法,因为在多线程环境下现在cache可能有该方法。


    image.png

    如果本类没有的话会调用getMethodNoSuper_nolock函数,然后依次调用search_method_list_inline、findMethodInSortedMethodList函数。我们可以看到findMethodInSortedMethodList是采用二分查找来获取方法的。


    image.png
    image.png
    image.png
    image.png

    当我们在本类里面找到方法时,它会进行goto done,然后会进入log_and_fill_cache函数,可以看到log_and_fill_cache函数内部调用了cache.insert()方法,也就是加入缓存里面了。需要注意的是哪个类调用就会加入哪个类的cache里面,也就是如果子类调用父类的方法,之后该方法会缓存到子类的cache。


    image.png
    image.png
    如果本类也没有方法,通过curClass = curClass->getSuperclass(),把curClass转换成父类,然后找父类的cache,cache如果没有在通过二分查找找方法列表。如果再没有把curClass转换成父类的父类依次查找。
    image.png

    总结:
    消息的慢速查找流程:

    • 调用lookUpImpOrForward
    • 看本类的cache里面有没有该方法
    • 如果没有看本类的methodList有没有该方法(二分查找)
    • 如果没有看父类的cache里面有没有该方法
    • 如果没有看父类的methodList有没有该方法(二分查找)
    • 然后逐级向上查找
    • 当父类是nil的时候也就是查找到NSObject的时候,都没有的话就会进入消息转发流程。

    相关文章

      网友评论

          本文标题:objc_msgSend解析

          本文链接:https://www.haomeiwen.com/subject/xsrgyrtx.html