美文网首页我的Object-C
iOS底层原理 - Runtime-02

iOS底层原理 - Runtime-02

作者: _曾梦想仗剑走天涯 | 来源:发表于2019-12-01 16:00 被阅读0次

    objc_msgSend执行流程

    OC中的方法调用,其实都是转换为objc_msgSend函数的调用
    objc_msgSend的执行流程可以分为3大阶段
    消息发送
    动态方法解析
    消息转发
    相信只要做了几年开发的都非常清楚runtime的消息转发机制,今天我就带大家一起来看看objc_msgSend执行流程

    objc_msgSend执行流程01-消息发送

    //前面这里都是汇编,有兴趣的可以了解一下
    ENTRY _objc_msgSend
    b.le    LNilOrTagged        
    b.eq    LReturnZero
    CacheLookup NORMAL  
    END_ENTRY _objc_msgSend
    MethodTableLookup
    

    bl __class_lookupMethodAndLoadCache3
    //从这里开始就查找方法

    IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
    {
        return lookUpImpOrForward(cls, sel, obj, 
                                  YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
    }
    
    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver) {
      IMP imp = nil;
      if (cache) {//如果缓存里面有方法 就直接返回方法地址
            imp = cache_getImp(cls, sel);
            if (imp) return imp;
       }
      if (imp) goto done; //如果缓存里没有就去方法列表里面查找方法 class_rw_t
        // Try this class's method lists.
        {
            Method meth = getMethodNoSuper_nolock(cls, sel);
            if (meth) {//如果找到了这个方法就填充到缓存里面去
                log_and_fill_cache(cls, meth->imp, sel, inst, cls);
                imp = meth->imp;
                goto done;
            }
        }
    
        //去父类的缓存里面查找方法列表  
        {
            unsigned attempts = unreasonableClassCount();
            for (Class curClass = cls->superclass;
                 curClass != nil;
                 curClass = curClass->superclass)
            {
                // Halt if there is a cycle in the superclass chain.
                if (--attempts == 0) {
                    _objc_fatal("Memory corruption in class list.");
                }
                
                // Superclass cache.
                imp = cache_getImp(curClass, sel);
                if (imp) {
                    if (imp != (IMP)_objc_msgForward_impcache) {
                        // 在父类中找到这个方法并且缓存到当前类中 
                        log_and_fill_cache(cls, imp, sel, inst, curClass);
                        goto done;
                    }
                    else {
                        break;
                    }
                }
                
                // Superclass method list.
                Method meth = getMethodNoSuper_nolock(curClass, sel);
                if (meth) {
                    log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                    imp = meth->imp;
                    goto done;
                }
            }
        }
    }
    

    //如果经历这些还是没有找到方法就会到第二个阶段 消息解析

    static void resolveMethod(Class cls, SEL sel, id inst)
    {
        runtimeLock.assertUnlocked();
        assert(cls->isRealized());
    
        if (! cls->isMetaClass()) {
            // try [cls resolveInstanceMethod:sel]
            resolveInstanceMethod(cls, sel, inst);
        } 
        else {
            // try [nonMetaClass resolveClassMethod:sel]
            // and [cls resolveInstanceMethod:sel]
            resolveClassMethod(cls, sel, inst);
            if (!lookUpImpOrNil(cls, sel, inst, 
                                NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
            {
                resolveInstanceMethod(cls, sel, inst);
            }
        }
    }
    

    //如果找到了就将方法填充到缓存里面去

    static void
    log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
    {
        cache_fill (cls, sel, imp, receiver);
    }
    
    void cache_fill(Class cls, SEL sel, IMP imp, id receiver) {
        mutex_locker_t lock(cacheUpdateLock);
        cache_fill_nolock(cls, sel, imp, receiver);
    }
    
    static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
    {
    //看到这个bucket_t 是不是有点眼熟,没错这就是我们在第一节里面讲到的方法缓存散列表,最终将sel作为key,函数方法地址作为value缓存到散列表中
        cache_t *cache = getCache(cls);
        // Scan for the first unused slot and insert there.
        // There is guaranteed to be an empty slot because the 
        // minimum size is 4 and we resized at 3/4 full.
        bucket_t *bucket = cache->find(sel, receiver);
        if (bucket->sel() == 0) cache->incrementOccupied();
        bucket->set<Atomic>(sel, imp);
    }
    
    static method_t *
    getMethodNoSuper_nolock(Class cls, SEL sel){
        runtimeLock.assertLocked();
        assert(cls->isRealized());
        // fixme nil cls? 
        // fixme nil sel?
        for (auto mlists = cls->data()->methods.beginLists(), 
                  end = cls->data()->methods.endLists(); 
             mlists != end;
             ++mlists)
        {
            method_t *m = search_method_list(*mlists, sel);
            if (m) return m;
        }
        return nil;
    }
    
    //这一步去方法列表中查找方法
    static method_t *search_method_list(const method_list_t *mlist, SEL sel)
    {
        int methodListIsFixedUp = mlist->isFixedUp();
        int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
        
        if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
            return findMethodInSortedMethodList(sel, mlist);
        } else {
            // Linear search of unsorted method list
            for (auto& meth : *mlist) {
                if (meth.name == sel) return &meth;
            }
        }
        return nil;
    }
    
    //通过排序方法列表找到对应的方法地址
    static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
    {
        assert(list);
      //二分法查找方法地址
        const method_t * const first = &list->first;
        const method_t *base = first;
        const method_t *probe;
        uintptr_t keyValue = (uintptr_t)key;
        uint32_t count;
        
        for (count = list->count; count != 0; count >>= 1) {
            probe = base + (count >> 1);
            uintptr_t probeValue = (uintptr_t)probe->name;
            if (keyValue == probeValue) {
                while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                    probe--;
                }
                return (method_t *)probe;
            }
            if (keyValue > probeValue) {
                base = probe + 1;
                count--;
            }
        }
        return nil;
    }
    //找到了对应的方法,就是我们最熟悉的method_t
    struct method_t {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
    
    //对这个大家都不陌生了吧 在runtime第一节里面我们讲到了
    
    屏幕快照 2019-11-29 01.40.23.png

    objc_msgSend执行流程02-动态方法解析

    **resolveInstanceMethod **
    **resolveClassMethod **
    对于这两个方法相信大家都很熟悉 到了这里就进入我们消息转发的第二步动态方法解析
    这里给大家演示一下最基本的动态添加方法

    //如果没有进行动态解析才会去动态解析
    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        if (sel == @selector(test)) {
            // 获取其他方法
            Method method = class_getInstanceMethod(self, @selector(other));
    
            // 动态添加test方法的实现
            class_addMethod(self, sel,
                            method_getImplementation(method),
                            method_getTypeEncoding(method));
    
            // 返回YES代表有动态添加方法
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    
    + (BOOL)resolveClassMethod:(SEL)sel
    {
        if (sel == @selector(test)) {
            // 第一个参数是object_getClass(self)
            class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
            return YES;
        }
        return [super resolveClassMethod:sel];
    }
    
    屏幕快照 2019-12-01 01.03.34.png

    objc_msgSend的执行流程03-消息转发

    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        if (aSelector == @selector(test)) {
            // objc_msgSend([[MJCat alloc] init], aSelector)
            return [[MJCat alloc] init];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
     //方法签名:返回值类型、参数类型
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        if (aSelector == @selector(test)) {
            return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    // NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
    //    anInvocation.target 方法调用者
    //    anInvocation.selector 方法名
    //    [anInvocation getArgument:NULL atIndex:0]
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
    //    anInvocation.target = [[MJCat alloc] init];
    //    [anInvocation invoke];
    
        [anInvocation invokeWithTarget:[[MJCat alloc] init]];
    }
    
    屏幕快照 2019-11-29 01.40.23.png

    面试题

    讲一下 OC 的消息机制
    OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
    objc_msgSend底层有3大阶段
    消息发送(当前类、父类中查找)、动态方法解析、消息转发

    具体应用
    利用关联对象(AssociatedObject)给分类添加属性
    遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
    交换方法实现(交换系统的方法)
    利用消息转发机制解决方法找不到的异常问题

    什么是Runtime?平时项目中有用过么?
    OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
    OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
    平时编写的OC代码,底层都是转换成了Runtime API进行调用

    相关文章

      网友评论

        本文标题:iOS底层原理 - Runtime-02

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