iOS-消息转发

runtime 介绍

runtime称为运行时,它区别于编译时
运行时 是代码跑起来,被装载到内存中的过程,如果此时出错,则程序会崩溃,是一个动态阶段。

编译时 是源代码翻译成机器能识别的代码的过程,主要是对语言进行最基本的检查报错,即词法分析、语法分析等,是一个静态的阶段。

消息转发

OC 中方法的调用其实本质上是消息的发送(objc_msgSend

消息转发的流程图

2251862-c7a4fe688fd72ac3.webp

一、 消息的查找
  • 快速查找:通过在方法的缓存里面查找IMP
  • 慢速查找
    根据源码lookUpImpOrForward方法分析

// 部分方法

// 查找方法流程
for (unsigned attempts = unreasonableClassCount();;) {
        // 查找缓存是否有这个方法
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            // 通过二分查找 去查找方法
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }
            // 获取父类
            if (slowpath((curClass = curClass->getSuperclass()) == 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.
        // 查找父类缓存
        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:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        // 将当前方法进行缓存
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
 done_unlock:
    runtimeLock.unlock();
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;

复制代码

1、判断方法缓存里面是否有这个方法
2、在当前类中通过二分查找getMethodNoSuper_nolock是否找到IMPgetMethodNoSuper_nolock ->search_method_list_inline->findMethodInSortedMethodList->findMethodInSortedMethodListfindMethodInSortedMethodList 的源码分析

template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);

    auto first = list->begin();
    auto base = first;
    decltype(first) 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)getName(probe);
        
        // 如果相等,则找到了
        if (keyValue == probeValue) {
            // 排除分类的方法影响
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
                probe--;
            }
            return &*probe;
        }
        // 大于 说明在右边,继续查找
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}
复制代码
  • 如果当前类中找到方法IMP,则跳到 done:方法,将当前方法进行缓存log_and_fill_cache,会执行代码cls->cache.insert(sel, imp, receiver);缓存方法。
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cls->cache.insert(sel, imp, receiver);
}
复制代码
  • 如果当前类没有找到,到父类去lookUpImpOrForward查找,当方法没有找到是会执行 resolveMethod_locked方法动态方法决议
二、 动态方法决议

当类和元类中都没有这个方法时,OC为了防止闪退给了一个处理的机会即动态方法决议

  • 快速转发 当慢速查找,以及动态方法决议均没有找到实现时,进行消息转发,首先是进行快速消息转发,即走到forwardingTargetForSelector方法

    1、 如果返回消息接收者,在消息接收者中还是没有找到,则进入另一个方法的查找流程

    2、 如果返回nil,则进入慢速消息转发

  • 慢速转发 执行到methodSignatureForSelector方法

    1、如果返回的方法签名为nil,则直接崩溃报错

    2、如果返回的方法签名不为nil,走到forwardInvocation方法中,对invocation事务进行处理,如果不处理也不会报错

resolveMethod_locked 源码解析

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    // 给一次机会处理IMP
    if (! cls->isMetaClass()) {
        // 对象方法
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        // 类方法
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
       // 主要是因为类方法在元类中是实例方法,所以还需要查找元类中实例方法的动态方法决议
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
复制代码
// 1: 快速转发

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));

    // runtime + aSelector + addMethod + imp
    return [super forwardingTargetForSelector:aSelector];
}

// 2: 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return nil;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
    anInvocation.target = [JCStudent alloc];
    // anInvocation 保存 - 方法
    [anInvocation invoke];
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享