美文网首页
objc库源码分析(3)-方法调用-消息发送

objc库源码分析(3)-方法调用-消息发送

作者: samstring | 来源:发表于2020-07-05 01:47 被阅读0次

    objc_msgSend()

    前面说过了,我们在写代码时候的会调用objc_msgSend系列的方法,然后再调用lookUpImpOrForward()方法,其实这只是一个笼统的说法。
    当我们调用方法时候,编译起会根据实际情况调用以下方法中的一种

    • objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
    • objc_msgSend_stret(id _Nullable self, SEL _Nonnull op, ...)
    • objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    • objc_msgSendSuper_stret(id _Nullable self, SEL _Nonnull op, ...)

    这些方法的定义在message.h文件中可以找到。这些方法的功能其实都是去寻找对象的方法,并去执行相应的方法。

    其中objc_msgSend()和objc_msgSendSuper()的接收者不一样。objc_msgSend()第一个参数是一个id类型的,而objc_msgSendSuper()第一个参数是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_super结构体中包含了两个对象,一个是id类型的方法接受者receiver,一个是receiver的父类。

    当我们调用对象的方法的时候,如[objectA test]的时候,会调用会objc_msgSend()方法,把 objectA 这个对象传给objc_msgSend()中的第一个参数self。

    当我们调用父类的方法的时候,如[super test]的时候(关于super的定义,在另外一篇文章中有提及到),会调用会objc_msgSendSuper()方法,把super对象传给objc_msgSendSuper()方法。

    objc_msgSend()和objc_msgSendSuper()区别在于,objc_msgSend()是从子类开始查找方法,而objc_msgSendSuper()是从父类开始查找方法。

    另外,在有如下定义

    #if !OBJC_OLD_DISPATCH_PROTOTYPES
    objc_msgSend(void /* id self, SEL op, ... */ )
       OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
    #else
    objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
       OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
    #endif
    

    其中OBJC_OLD_DISPATCH_PROTOTYPES的意思是在调用objc_msgSend()时,是否需要转换成合适的方法指针。如OBJC_OLD_DISPATCH_PROTOTYPES为YES,我们在调用objc_msgSend()就需要声明方法的返回值类型和参数类型,如

    ((int (*)(id, SEL, NSString *, BOOL)) objc_msgSend)((id)obj,@selector(viewWillAppear:),YES);
    

    如果OBJC_OLD_DISPATCH_PROTOTYPES为NO,在调用时,我们可以用如下的方式去调用

    objc_msgSend((id)obj,@selector(viewWillAppear:),YES);
    

    OBJC_OLD_DISPATCH_PROTOTYPES的值可以通过Build Setting中去设置在 Enable Strict Checking of objc_msgSend Calls 设置。


    在objc库中,objc_msgSend()的实现是不开源的,但是可以通过反编译去找到伪代码的实现。在网上找了一份(来源https://www.jianshu.com/p/df6629ec9a25

    id  objc_msgSend(id receiver, SEL op, ...)
    {
    
        //1............................ 对象空值判断。
        //如果传入的对象是nil则直接返回nil
        if (receiver == nil)
            return nil;
        
       //2............................ 获取或者构造对象的isa数据。
        void *isa = NULL;
        //如果对象的地址最高位为0则表明是普通的OC对象,否则就是Tagged Pointer类型的对象
        if ((receiver & 0x8000000000000000) == 0) {
            struct objc_object  *ocobj = (struct objc_object*) receiver;
            isa = ocobj->isa;
        }
        else { //Tagged Pointer类型的对象中没有直接保存isa数据,所以需要特殊处理来查找对应的isa数据。
            
            //如果对象地址的最高4位为0xF, 那么表示是一个用户自定义扩展的Tagged Pointer类型对象
            if (((NSUInteger) receiver) >= 0xf000000000000000) {
                
                //自定义扩展的Tagged Pointer类型对象中的52-59位保存的是一个全局扩展Tagged Pointer类数组的索引值。
                int  classidx = (receiver & 0xFF0000000000000) >> 52
                isa =  objc_debug_taggedpointer_ext_classes[classidx];
            }
            else {
                
                //系统自带的Tagged Pointer类型对象中的60-63位保存的是一个全局Tagged Pointer类数组的索引值。
                int classidx = ((NSUInteger) receiver) >> 60;
                isa  =  objc_debug_taggedpointer_classes[classidx];
            }
        }
        
       //因为内存地址对齐的原因和虚拟内存空间的约束原因,
       //以及isa定义的原因需要将isa与上0xffffffff8才能得到对象所属的Class对象。
        struct objc_class  *cls = (struct objc_class *)(isa & 0xffffffff8);
        
       //3............................ 遍历缓存哈希桶并查找缓存中的方法实现。
        IMP  imp = NULL;
        //cmd与cache中的mask进行与计算得到哈希桶中的索引,来查找方法是否已经放入缓存cache哈希桶中。
        int index =  cls->cache.mask & op;
        while (true) {
            
            //如果缓存哈希桶中命中了对应的方法实现,则保存到imp中并退出循环。
            if (cls->cache.buckets[index].key == op) {
                  imp = cls->cache.buckets[index].imp;
                  break;
            }
            
            //方法实现并没有被缓存,并且对应的桶的数据是空的就退出循环
            if (cls->cache.buckets[index].key == NULL) {
                 break;
            }
            
            //如果哈希桶中对应的项已经被占用但是又不是要执行的方法,则通过开地址法来继续寻找缓存该方法的桶。
            if (index == 0) {
                index = cls->cache.mask;  //从尾部寻找
            }
            else {
                index--;   //索引减1继续寻找。
            }
        } /*end while*/
    
       //4............................ 执行方法实现或方法未命中缓存处理函数
        if (imp != NULL)
             return imp(receiver, op,  ...); //这里的... 是指传递给objc_msgSend的OC方法中的参数。
        else
             return objc_msgSend_uncached(receiver, op, cls, ...);
    }
    
    /*
      方法未命中缓存处理函数:objc_msgSend_uncached的C语言版本伪代码实现,这个函数也是用汇编语言编写。
    */
    id objc_msgSend_uncached(id receiver, SEL op, struct objc_class *cls)
    {
       //这个函数很简单就是直接调用了_class_lookupMethodAndLoadCache3 来查找方法并缓存到struct objc_class中的cache中,最后再返回IMP类型。
      IMP  imp =   _class_lookupMethodAndLoadCache3(receiver, op, cls);
      return imp(receiver, op, ....);
    }
    
    IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
    {
        return lookUpImpOrForward(cls, sel, obj, 
                                  YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
    }
    

    可以看到,objc_msgSend()方法的执行流程是

    1 判断对象是否为空
    2 处理tagget pointer,找到相应的类,
    3 判断类的缓存列表里面是否找到方法的实现,如果找到,则执行
    4 如果缓存列表里面没找到方法实现,则调用lookUpImpOrForward查找方法


    方法缓存

    待补充


    方法查找lookUpImpOrForward()

    lookUpImpOrForward方法的实现如下

    IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
    {
        const IMP forward_imp = (IMP)_objc_msgForward_impcache;
        IMP imp = nil;
        Class curClass;
    
        runtimeLock.assertUnlocked();
    
        // Optimistic cache lookup 去方法的缓存列表找方法
        if (fastpath(behavior & LOOKUP_CACHE)) {
            imp = cache_getImp(cls, sel);
            if (imp) goto done_nolock;//如果找到方法,则跳转到最下方的done_nolock
        }
    
        // runtimeLock is held during isRealized and isInitialized checking
        // to prevent races against concurrent realization.
      
    
        // runtimeLock is held during method search to make
        // method-lookup + cache-fill atomic with respect to method addition.
        // Otherwise, a category could be added but ignored indefinitely because
        // the cache was re-filled with the old value after the cache flush on
        // behalf of the category.
        //
    
        runtimeLock.lock();
    
        // We don't want people to be able to craft a binary blob that looks like
        // a class but really isn't one and do a CFI attack.
        //  CFI attack 破坏机器码执行来控制程序行为的攻击
        // To make these harder we want to make sure this is a class that was
        // either built into the binary or legitimately registered through
        // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
        //
        // TODO: this check is quite costly during process startup.
        checkIsKnownClass(cls);//检查类的来源
    
        if (slowpath(!cls->isRealized())) {//如果类没有标志为已实现,则需要判断锁是否释放掉了
            
            cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);//这个主要是判断是否解锁,如果锁释放掉,则重新枷锁
            // runtimeLock may have been dropped but is now locked again
        }
    
        //如果类没有初始化,则初始化类
        if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
            //#define fastpath(x) (__builtin_expect(bool(x), 1))
            //#define slowpath(x) (__builtin_expect(bool(x), 0))
            //__builtin_expect作用是"允许程序员将最有可能执行的分支告诉编译器"。这个指令的写法为:__builtin_expect(EXP, N)。意思是:EXP==N的概率很大。
    
            cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
            // runtimeLock may have been dropped but is now locked again
    
            // If sel == initialize, class_initialize will send +initialize and 
            // then the messenger will send +initialize again after this 
            // procedure finishes. Of course, if this is not being called 
            // from the messenger then it won't happen. 2778172
            //如果当前调用的方法是initialize方法,而且是由messenger(一般是我们自己调用)调用,会调用两次,但是第一次调用initialize就会把类标记为initialized
        }
    
        runtimeLock.assertLocked();
        curClass = cls;
    
        // The code used to lookpu the class's cache again right after
        // we take the lock but for the vast majority of the cases
        // evidence shows this is a miss most of the time, hence a time loss.
        //
        // The only codepath calling into this without having performed some
        // kind of cache lookup is class_getInstanceMethod().
    
        //重新查找方法缓存
        for (unsigned attempts = unreasonableClassCount();;) {
            // curClass method list.
            //查找当前类的方法列表
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp;
                goto done;
            }
    
            if (slowpath((curClass = curClass->superclass) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                //如果找不到在类中,找不到方法。而且该类的父类为空,则进入消息转发流程
                imp = forward_imp;
                break;
            }
    
            // Halt if there is a cycle in the superclass chain.
            if (slowpath(--attempts == 0)) {
                _objc_fatal("Memory corruption in class list.");
            }
    
            // Superclass cache.查找父类的缓存,如果还是没有找到方法,则返回forward_imp的实现
            imp = cache_getImp(curClass, sel);
            if (slowpath(imp == forward_imp)) {
                // Found a forward:: entry in a superclass.
                // Stop searching, but don't cache yet; call method
                // resolver for this class first.
                break;
            }
            if (fastpath(imp)) {
                // Found the method in a superclass. Cache it in this class.
                goto done;
            }
        }
    
        // No implementation found. Try method resolver once.
        //如果没有找到方法实现,则尝试动态方法解析
        if (slowpath(behavior & LOOKUP_RESOLVER)) {
            behavior ^= LOOKUP_RESOLVER;
            return resolveMethod_locked(inst, sel, cls, behavior);
        }
    
     done:
        log_and_fill_cache(cls, imp, sel, inst, curClass);//缓存方法调用
        runtimeLock.unlock();
     done_nolock:
        if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
            return nil;
        }
        return imp;
    }
    

    这段代码的执行流程如图

    lookUpImpOrForward执行流程.jpg

    lookUpImpOrForward()执行完以后,把imp返回给objc_msgSend_uncached()方法。objc_msgSend_uncached()方法会去直接执行imp。

    由于lookUpImpOrForward()中有可能返回的是方法的具体实现,有可能返回的是消息转发forward_imp。如果返回的是forward_imp,那么将进入消息转发流程。
    消息转发流程发在下一篇文章讲述。

    相关文章

      网友评论

          本文标题:objc库源码分析(3)-方法调用-消息发送

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