iOS底层探索 --- 类的加载(中)

image

这里我们将接着上一篇文章iOS底层探索 --- 类的加载(上)继续探索.

我们来简单的回忆一下:
我们从 _objc_init -> _dyld_objc_notify_register -> map_images -> _read_images;这样一路追踪到_read_images中。我们看到了加载镜像的10个步骤。

在这10个步骤中,有对一些错乱信息的处理,类的加载,协议,分类等等信息的处理。此时我们的重点是类的加载。所以重点就在第9步。

在这里,关键的函数就是 realizeClassWithoutSwift。所以今天我们重点来探索一下这个函数的逻辑。

在了解了我们要做什么的之前,先做一下知识点的补充,因为在realizeClassWithoutSwift中有一些知识点,需要我们提前了解一下。

  • ro:属于clean memory,即\color{red}{在编译时期确定的内存空间 -- 只读};加载后不会改变内存。
  • rw:属于dirty memory,即\color{red}{在运行时产生的内存 -- 可读可写};可以向类中添加属性、方法等,是在运行时可以改变的内存。
  • rwe:相当于类的额外信息,因为在使用过程中,只有很少的类会真正改变其内容,所以为了避免资源浪费就有了rwe

运行时如果需要动态向类中添加方法、协议等,会创建rwe,并将ro的数据优先attach(附加)rwe中。在读取时会优先返回rwe的数据;如果rwe没有被初始化,则返回ro。(也即是说:① 有扩展,从rwe中取;② 没有扩展,从ro中取。)

rw中包含 ro,rwe。其目的是为了让dirty memeory占用更少的内存,将rw可变的部分抽取出来作为rwe

clean memeory越多越好,dirty memory越少越好。因为iOS系统底层是虚拟内存机制,在内存不足的情况下,会将一部分内存回收;之后使用时再从磁盘中加载。

clean memeory是可以从磁盘中重新加载的内存,例如动态库,Mach-O文件等。

dirty memory是运行时产生的数据,不能从磁盘中重新加载;所以必须一直占用内存。

当系统物理内存紧张时,会回收clean memory;如果dirty memeory过大,则会直接回收掉。

设计rorwrwe的目的是为了更好更细致的区分clean memorydirty memeory


realizeClassWithoutSwift

同样的,我们首先来看一下源码是怎么写的:

/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls, 对类 cls 执行首次初始化
* including allocating its read-write data. 包括分配其读写数据
* Does not perform any Swift-side initialization. 不执行任何 Swift 端初始化
* Returns the real class structure for the class. 返回类的真实类结构
* Locking: runtimeLock must be write-locked by the caller 锁定:runtimeLock 必须由调用者写锁定
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?

    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    // This assumes that none of those classes have Swift contents,
    //   or that Swift's initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {
            // This is also propagated by addSubclass()
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate
            // from root class to root metaclass
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    methodizeClass(cls, previously);

    return cls;
}

大家注意看官方对该函数的注释,这里给出了翻译。realizeClassWithoutSwift方法的主要所用就是实现类,官方注释写的很明白Returns the real class structure for the class,返回一个真实的类结构。

主要有以下几个步骤:

  • 1、读取data数据,设置rorw
  • 2、递归realizeClassWithoutSwift(这里回忆一下isa走位图)。
  • 3、设置类的一些相关信息,子类,父类等等。
  • 4、methodizeClass,附加类别。

1、读取data数据,设置rorw

在上面我们已经介绍过rorw的相关定义。

image


2、递归realizeClassWithoutSwift

这里会递归调用realizeClassWithoutSwift,对于当前类的父类元类进行初始化。

image


3、设置类的一些相关信息,子类,父类等等

在得到了父类元类之后,对当前类进行配置。当然,上面有递归调用,所以,或有if判断,看一下当前类的类型。

image


4、methodizeClass,附加类别

我们跟踪进methodizeClass之后,首先还是要阅读官方注释;通过过官方注释我们得知,这个方法是修正cls方法列表协议列表属性列表

/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (const auto& meth : rw->methods()) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(meth.name()));
        }
        ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());
    }
#endif
}
  • 这里虽然官方注释是Attach categories,但是大家不要认为只是附加分类中的方法。因为在源码中有这样一段注释,大家仔细看一下:
    image

    看一看到这里对于方法,属性的处理,是包含类本身的方法,属性等信息的。对于协议也是一样的,因为源码中也获取了baseProtocols:
    image

最终,方法列表属性列表协议列表,都附加到了rwe中。


4.1 prepareMethodLists

上面我们看到,对于方法列表的处理,并不是直接添加到调用rwe->methods.attachLists(&list, 1);。在附加之前,还调用了prepareMethodLists函数;那么这个函数又是做什么的呢?我们跟进去看一下,最终发现了这样一段代码。(注意:这里list传入的是&list

image

也就是说,方法列表在附加到rwe中之前,做了一次排序。(通过sore可以推测,注意上图中红圈。)


4.2 fixupMethodList

上面我们追踪到了fixupMethodList,推测到这是一个对方法列表进行排序的函数。那究竟是怎么排序的呢?我们进入函数看一下:

image

可以看到,方法列表是根据selecor address来进行排序的。

同时,在排序之前,根据if的判断,还会进行一次name(返回的SEL)的设置。(看图中,红框上方的代码块。)

image

  • 这个地方我们可以通过打印验证一下。我们在fixupMethodList中,打印一下,排序前后的methodlist:
    image
  • 我们自定一个类,并调用一下,(注意:这里要调用一下load方法,不然系统会默认为懒加载)
  • 接下来,运行工程(源码工程),在控制台的打印里面搜索我们自定义的方法(因为有系统的一些打印,会比较多。),可以看到方法按照地址排了序:


    image

补充:在方法的慢速查找,会遇到一个二分查找的方法。二分查找的基础就是方法列表已经被排序。

这里我们用伪代码简单实现以下二分查找

image

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,248评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,681评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,443评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,475评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,458评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,185评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,451评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,112评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,609评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,083评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,163评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,803评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,357评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,357评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,590评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,636评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,925评论 2 344

推荐阅读更多精彩内容