OC底层方法的本质、查找流程

1. 前言

前面的文章了解了OC对象的本质、类的本质以及方法缓存的原理,那么这篇文章将来分析一下OC方法底层的原理。

2. 方法的本质

作为一个iOS开发者,每个人多多少少都会对方法的本质有所了解,最简单的理解就是发送消息,毫无疑问,确实是这样的,那么是怎么发送的,接受者是谁,消息又是什么,都有什么参数呢?
下面看一组代码:

@interface GYMPerson : NSObject
- (void)playGame;
+ (void)loveLife;
@end

@interface GYMDeveloper : GYMPerson
- (void)writeCode;
+ (void)loveJob;
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // GYMDeveloper类继承了GYMPerson类
        GYMDeveloper *developer = [GYMDeveloper alloc];
        [developer writeCode];      // 对象调用本类实例方法
        [developer playGame];       // 对象调用父类实例方法
        [GYMDeveloper loveJob];     // 子类调用本类类方法
        [GYMDeveloper loveLife];    // 子类调用父类类方法
    }
    return 0;
}

当用clang将main.m转成main.cpp时,则main函数如下:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        GYMDeveloper *developer = ((GYMDeveloper *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("GYMDeveloper"), sel_registerName("alloc"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)developer, sel_registerName("writeCode"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)developer, sel_registerName("playGame"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("GYMDeveloper"), sel_registerName("loveJob"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("GYMDeveloper"), sel_registerName("loveLife"));
    }
    return 0;
}

是不是看起来有些乱?我们整理一下:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        GYMDeveloper *developer = (GYMDeveloper *)objc_msgSend(objc_getClass("GYMDeveloper"), sel_registerName("alloc"));
        objc_msgSend(developer, sel_registerName("writeCode"));
        objc_msgSend(developer, sel_registerName("playGame"));
        objc_msgSend(objc_getClass("GYMDeveloper"), sel_registerName("loveJob"));
        objc_msgSend(objc_getClass("GYMDeveloper"), sel_registerName("loveLife"));
    }
    return 0;
}

代码精简后,可以看到不管是创建对象,还是对象调用方法以及类调用类方法,都涉及到了一个重要的函数objc_msgSend,其中有两个重要的参数,一个是消息的接受者,一个是消息名称。
所以OC底层方法的实现就是发送消息。

3. 方法的查找流程

方法的查找流程分为快速查找流程和慢速查找流程。

3.1 快速查找流程

快速查找流程由汇编语言编写,从_objc_msgSend开始:

  1. 通过消息接受者的isa,查找其类或者元类(实例方法通过isa找对象的类,类方法通过isa找类的元类)。
  2. 当找到类或者元类后,通过内存偏移,找到类的cache属性,然后调用CacheLookUp
  3. CacheLookUp流程中,将方法编号转换成对应的key,再去cache缓存中找该方法。
  4. 如果该方法已经在cache中缓存了,那么直接返回方法的imp
  5. 如果在cache中找不到,那么进入JumpMiss流程,调用__objc_msgSend_uncached
  6. __objc_msgSend_uncached中,调用MethodTableLookup,到此则意味着快速查找流程结束了。
  7. MethodTableLookup中,则准备一些相关的参数等,然后调用__class_lookupMethodAndLoadCache3进入慢速查找流程。

3.2 慢速查找流程

在上面进入__class_lookupMethodAndLoadCache3时,即意味着开始了慢速查找流程,此时将从汇编模式转成C代码模式,其对应的C函数为:

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

在这个函数里面又调用了lookUpImpOrForward函数,具体说明请看注释:

/**
这个函数主要分为四部分:
1. 查找缓存中是否缓存了该方法,这个方法的cache有可能在别的地方调用的时候传true。
2. 判断当前的类是否合法,是否已经实例化,如果没有实例化,那么需要对类,类的父类等,以及类元类,元类的元类进行实例化。
3. 在当前类中的方法列表中查找该方法,如果查找到直接返回IMP。
4. 在父类的方法列表中查找该方法,如果父类没有,则循环找父类的父类,直到老祖宗级别,找到返回IMP。
5. 到这步如果还没有找到,那么进入方法转发阶段,这部分后面再讲。
6. 如果方法转发成功,万事大吉,否则崩溃报错。
*/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    // 在缓存中查找方法的IMP,如果有直接返回IMP。
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }
    
    runtimeLock.lock();
    checkIsKnownClass(cls);
    
	//如果当前类没有实例化,那么调用realizeClass进行实例化。
    if (!cls->isRealized()) {
    	/**
    	该函数中主要对类的ro、rw进行赋值,以及该类继承链上的父类,元类以及元类的元类等的rw、ro赋值等操作。
    	此操作主要确保后续方法查找过程中不会出现异常。
    	*/
        realizeClass(cls);
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlock();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.lock();
    }

 retry:    
    runtimeLock.assertLocked();

    // Try this class's cache.
	// 在缓存中查找方法的IMP,如果有直接返回IMP。
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.
    {
    	// 在当前类的方法列表中查找该方法
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
        	// 如果顺利找到了方法,那么调用log_and_fill_cache进行方法缓存,此方法最终会调用上一篇文章(cache_t分析)中的方法缓存方法cache_fill_nolock
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.
    {
        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返回。
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            // 如果缓存中没有,则在父类的方法列表中查找该方法。
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
            	// 如果找到了,在父类缓存该方法,并返回IMP。
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.
	// 如果执行到了这里,那么说明本类和继承链上的所有父类都没有该方法,那么则进行一次消息转发。
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
	// 如果消息转发都失败了,那么会执行到这里,程序崩溃报错了。
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);
 done:
    runtimeLock.unlock();
    return imp;
}

imp_objc_msgForward_impcache的时候,则会走入汇编流程,最终调用下面的方法发出报错信息:

// Default forward handler halts the process.
__attribute__((noreturn)) void 
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}

3.3 方法查找小结

实例方法的查找流程,如下图:
在这里插入图片描述

类方法的查找流程,如下图:
在这里插入图片描述

4. 结束语

本篇文章主要介绍了方法的本质,以及方法的查找流程。

方法的本质即是给调用方法的对象发送消息,并带上方法编号这个参数,然后进行方法查找。

方法查找分为快速查找慢速查找。快速查找则通过汇编代码在cache中进行查找,当快速查找无结果的时候,进入慢速查找,遍历当前类或者元类的方法列表进行查找。

那么如果两种查找都找不到该怎么办呢?直接崩溃吗?
当然不是了,底层代码还给我们留了个转发的机制,即消息转发

下一篇文章将介绍方法的消息转发,欢迎来阅读噢!

如果路过的朋友觉得这篇文章有所帮助,还望给个赞哦!

©️2020 CSDN 皮肤主题: 创作都市 设计师:CSDN官方博客 返回首页