底层原理-15-类的加载(下)

600 阅读9分钟

继续上一篇探索下分类的加载流程。上一篇我们探索类的加载其中methodizeClass方法有关于分类的加载。

1.分类加载推导

image.png继续查看attachToClass方法,

image.png发现添加分类的方法attachCategories,我们用反推的方法判断哪里调用了它,发现有2处调用了,一处就是我们上面attachToClass(第一处)中调用。

  • 再看load_categories_nolock(第二处)调用

image.png 继续查找load_categories_nolock,有2处调用

image.png第一处在loadAllCategories调用。继续搜索loadAllCategories得到

image.png 查看第二处: image.png第二处在之前熟悉的read_images调用。其中只有当didInitialAttachCategories = true的时候才会进入。而didInitialAttachCategories默认是false,在上面loadAllCategories方法中会设置didInitialAttachCategoriestrue
根据分析的流程可以得出添加分类的大概流程,如下:

未命名文件.jpg

2. 分类的主要流程分析。

我们顺着代码流程分析一下分类的加载

2.1 methodizeClass简单分析

static void methodizeClass(Class cls, Class previously)
{
......
// Attach categories.添加分类
    if (previously) {
        if (isMeta) {
            //元类的话
            objc::unattachedCategories.attachToClass(cls, previously,                                             ATTACH_METACLASS);
        } else {
            //类
            objc::unattachedCategories.attachToClass(cls, previously,

                                                     ATTACH_CLASS_AND_METACLASS);

        }

    }

    objc::unattachedCategories.attachToClass(cls, cls,

                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
.....
}

previouslymethodizeClass方法带入进来的,它来自realizeClassWithoutSwift()全局搜索

image.png 发现调用处穿的都是nil,这个参数应该是苹果内部测试的自定义参数。那么上面正常情况不会进入previously的判断条件而是走下面的objc::unattachedCategories.attachToClass(cls, cls,isMeta ? ATTACH_METACLASS : ATTACH_CLASS);,previously就是cls。

2.2 attachToClass分析

void attachToClass(Class cls, Class previously, int flags)

    {

        runtimeLock.assertLocked();

        ASSERT((flags & ATTACH_CLASS) ||

               (flags & ATTACH_METACLASS) ||

               (flags & ATTACH_CLASS_AND_METACLASS));

        auto &map = get();

        auto it = map.find(previously);//查找分类previously是cls

    
        bool isMeta = cls->isMetaClass();

        const char *mangledName = cls->nonlazyMangledName();

        if (strcmp(mangledName, "LGPerson") == 0)

        {

            if (!isMeta) {

                printf("%s -LGPerson....\n",__func__);

            }

        }

        if (it != map.end()) {

            //主类没有实现load,分类开始加载,主类会被迫实现

            category_list &list = it->second;

            if (flags & ATTACH_CLASS_AND_METACLASS) {

                //元类的进入

                int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;

                attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);//实例方法

                attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);//类方法

            } else {

                attachCategories(cls, list.array(), list.count(), flags);//不是元类

            }

            map.erase(it);

        }

    }

主要是将分类添加到主类,根据加载时机和类型。

2.3 attachCategories分析

// Attach method lists and properties and protocols from categories to a class.

// Assumes the categories in cats are all loaded and sorted by load order, 

// oldest categories first.将类别中的方法列表、属性和协议附加到类中。

static void

attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,

                 int flags)

{

    if (slowpath(PrintReplacedMethods)) {

        printReplacements(cls, cats_list, cats_count);

    }

    if (slowpath(PrintConnecting)) {

        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",

                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",

                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");

    }


    /*

     * Only a few classes have more than 64 categories during launch.

     只有少数类在发布时拥有超过64个类别

     * This uses a little stack, and avoids malloc.

     *这使用了一个小堆栈,避免了malloc

     * Categories must be added in the proper order, which is back

     * to front. To do that with the chunking, we iterate cats_list

     * from front to back, build up the local buffers backwards,

     * and call attachLists on the chunks. attachLists prepends the

     * lists, so the final result is in the expected order.

     */

    constexpr uint32_t ATTACH_BUFSIZ = 64;

    method_list_t   *mlists[ATTACH_BUFSIZ];

    property_list_t *proplists[ATTACH_BUFSIZ];

    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;

    uint32_t propcount = 0;

    uint32_t protocount = 0;

    bool fromBundle = NO;

    bool isMeta = (flags & ATTACH_METACLASS);

    auto rwe = cls->data()->extAllocIfNeeded();//创建rwe如果没有创建的话,把主类的data赋值给它

    

    const char *mangledName = cls->nonlazyMangledName();

    if (strcmp(mangledName, "LGPerson") == 0)

    {

        if (!isMeta) {

            printf("attachCategories****LGPerson....\n");

        }

    }

    for (uint32_t i = 0; i < cats_count; i++) {

        auto& entry = cats_list[i];
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);

        if (mlist) {

            if (mcount == ATTACH_BUFSIZ) {

                //mcount = 64进入 mlists是一个二维数组

                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);//准备方法列表进行排序

                rwe->methods.attachLists(mlists, mcount);//赋值给rwe

                mcount = 0;

            }

            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;//把mlist赋值给mlists,mcount++。从后往前。

            fromBundle |= entry.hi->isBundle();

        }
        property_list_t *proplist =

            entry.cat->propertiesForMeta(isMeta, entry.hi);

        if (proplist) {

            if (propcount == ATTACH_BUFSIZ) {

                rwe->properties.attachLists(proplists, propcount);

                propcount = 0;

            }

            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;

        }
        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);

        if (protolist) {

            if (protocount == ATTACH_BUFSIZ) {

                rwe->protocols.attachLists(protolists, protocount);

                protocount = 0;

            }

            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;

        }

    }

    if (mcount > 0) {

        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,

                           NO, fromBundle, __func__);//排序 mlists是一个二维数组,+ ATTACH_BUFSIZ - mcount对mlists进行指针平移

        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);

        if (flags & ATTACH_EXISTING) {

            flushCaches(cls, __func__, [](Class c){

                // constant caches have been dealt with in prepareMethodLists

                // if the class still is constant here, it's fine to keep

                return !c->cache.isConstantOptimizedCache();

            });

        }

    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);

}

首先定义一个最大容量64的二维数组mlists,数组的类型是method_list_t

image.png 取出mlist赋值给mlists,从后往前进行赋值

image.png

rwe 进行判断是否开辟,没有开辟的话进行开辟extAllocIfNeeded

class_rw_ext_t *extAllocIfNeeded() {

        auto v = get_ro_or_rwe();

        if (fastpath(v.is<class_rw_ext_t *>())) {//判断是否存在

            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);//存在就获取

        } else {

            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));//不存在创建

        }

    }
class_rw_ext_t *

class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)

{

    runtimeLock.assertLocked();
    
    auto rwe = objc::zalloc<class_rw_ext_t>();//创建class_rw_ext_t类型的

    rwe->version = (ro->flags & RO_META) ? 7 : 0;

    method_list_t *list = ro->baseMethods();

    if (list) {

        if (deepCopy) list = list->duplicate();

        rwe->methods.attachLists(&list, 1);

    }
    // See comments in objc_duplicateClass

    // property lists and protocol lists historically

    // have not been deep-copied

    //

    // This is probably wrong and ought to be fixed some day

    property_list_t *proplist = ro->baseProperties;

    if (proplist) {

        rwe->properties.attachLists(&proplist, 1);

    }
    protocol_list_t *protolist = ro->baseProtocols;

    if (protolist) {

        rwe->protocols.attachLists(&protolist, 1);

    }

    set_ro_or_rwe(rwe, ro);

    return rwe;

}

主要是判断是否存在rwe,不存在就创建,extAlloc过程是把本类的data数据存储到rwe中。

image.png

2.4 attachLists分析

void attachLists(List* const * addedLists, uint32_t addedCount) {

        if (addedCount == 0) return;

        if (hasArray()) {

            // many lists -> many lists 多维数组处理——>多维数组

            uint32_t oldCount = array()->count;//计算旧的数组大小

            uint32_t newCount = oldCount + addedCount;//新数组大小=旧的大小+添加的

            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));//根据新的所需大小开辟新数组

            newArray->count = newCount;//设置新数组大小

            array()->count = newCount;//设置数组大小为新的素组大小

            for (int i = oldCount - 1; i >= 0; i--)

                newArray->lists[i + addedCount] = array()->lists[i];//给新数组赋值从旧数组位置开始取,从后往前

            for (unsigned i = 0; i < addedCount; i++)

                newArray->lists[i] = addedLists[i];//新添加的数组从前往后赋值

            free(array());

            setArray(newArray);//把二维数组重新赋值。

            validate();

        }

        else if (!list  &&  addedCount == 1) {

            // 0 lists -> 1 list

            list = addedLists[0];//第一次进来的时候直接取数组列表第一个元素,赋值给list,是一个一维数组

            validate();

        } 

        else {

            // 1 list -> many lists 一个二维数组到多个二维数组

            Ptr<List> oldList = list;//取出之前的-维数组

            uint32_t oldCount = oldList ? 1 : 0;//运算符,判断是否有旧的数组存在,存在就是1,不存在是0

            uint32_t newCount = oldCount + addedCount;//新的二维数组大小 = 添加的 + 之前的

            setArray((array_t *)malloc(array_t::byteSize(newCount)));//根据新的数组大小开辟新的空间

            array()->count = newCount;//设置数组大小

            if (oldList) array()->lists[addedCount] = oldList;//存在之前的数组,把二维数组的末尾赋值就的一维数组

            for (unsigned i = 0; i < addedCount; i++)

                array()->lists[i] = addedLists[i];//遍历添加二维数组添加,从o位置开始赋值,添加的

            validate();

        }

    }

attachLists插入表主要是添加二维数组,把老的二维数组变成新的。二维数组的类型是array_t

struct array_t {

        uint32_t count;//数组个数

        Ptr<List> lists[0];//数组的0号位置
        
        static size_t byteSize(uint32_t count) {

            return sizeof(array_t) + count*sizeof(lists[0]);

        }

        size_t byteSize() {

            return byteSize(count);//大小

        }

    };
  1. many lists -> many lists 先计算出之前的二维数组大小,新的数组大小。根据新的大小开辟新的array_t数组,之后把老的array_t数组的值从后往前塞进新的array_t数组。
  2. 0 lists -> 1 list
    二维数组为空,添加的数量为1.第一次进来。addedLists只有一个值直接取0位置的值,赋值给list。
  3. 1 list -> many lists
    取出之前的二维数组只有一个值,之前看过array_t的数据格式,知道Ptr<List> lists[0]。计算出新添加的二维数组大小,根据新的大小创建新的array_t数组。先吧就的值赋值到新数组的最后一位,因为只有一个不用循环;之后循环从初始位置赋值
    流程如下: 未命名文件-6.jpg

3. 主类和分类加载时机

我们之前探究到类的加载分为懒加载非懒加载,那么按照节约内存的情况,分类也应该类似。但是我们分析了分类加载依赖于类,于是就有4种情况,我们一一分析。 未命名文件-4.jpg

3.1 主类和分类都实现load方法

我们在attachCategories中添加断点,进入断点后,我们打印bt信息。

image.png 得出流程load_images-->loadAllCategories-->load_categories_nolock-->attachCategories

3.2主类不实现+load,分类实现+load

在main函数前打个断点防止进入懒加载流程

image.png 发现没有进入attachCategories,明明已经分类实现了+load,应该走非懒加载方法进入attachCategories但是却没有。那么到底分类的信息添加到类没有?带着疑问我在

image.png 说明这个在实现类的时候也读取了分类的数据。主类没有进行+load方法调用,也没有发送消息进入慢速查找流程实现类。那么如何进入realizeClassWithoutSwift?上一步

image.png 这段代码是实现非惰性类也就是+load方法实现的。是prepare_load_methods方法回调。实现分类+load方法时候会把主类也实现,读取data()包含了分类和主类的数据,而数据在编译的时候就确定了。得出流程 _read_images-->readClass-->realizeClassWithoutSwift

3.2.1 主类不实现+load,多个分类,只有一个分类实现+load;

新建分类LGB,LGC,

image.png

image.png 不实现+load方法,继续上面分析。

image.png 结果和上面只有一个+load一样。流程_read_images-->readClass-->realizeClassWithoutSwift

3.2.2 主类不实现+load,分类实现2个以上+load;

根据自定义断点可知。先进入

image.png 之后进入load_categories_nolock,加载分类。

image.png 之后进入prepare_load_methods

image.png 查找分类中实现+load的分类列表,并实现主类realizeClassWithoutSwift

image.png 在主类实现好进入methodizeClass,继续下面的流程,打印堆栈信息

image.png 流程如下:load_images-->loadAllCategories(加载分类)-->load_categories_nolock(处理分类数据)-->prepare_load_methods(准备)-->realizeClassWithoutSwift(实现类)-->methodizeClass-->attachToClass-->attachCategories(添加分类)

3.3 主类实现+load,分类不实现+load

分类都不实现+load方法,只有主类实现+load。主类走我们之前探索类的时候实现类代码中的非懒加载代码块

image.png 打印一下堆栈的信息,整理下流程

image.png 流程如下:map_images-->map_images_nolock-->_read_images-->realizeClassWithoutSwift-->methodizeClass在编译的时候确定了类和分类的data

3.4 主类和分类不实现+load

image.png 当第一次调用类发送消息的时候,不管调用的是类的方法还是分类的方法,走类的懒加载流程。此时流程:lookUpImpOrForward -->realizeClassMaybeSwiftMaybeRelock-->realizeClassWithoutSwift

image.png

4.总结

  • 分类加载流程分析可以通过反推的方法,去查找它的流程。
  • 分类添加到主类的时候会把数据写入rwe,判断是否已经存在rwe,不存在就创建一个同时把主类的数据也写入。分类的数据写入通过attachList写入属性,方法列表,协议等。写入过程是根据添加表的个数加之前添加的个数开辟新的数组,之前的数组值从后往前插入新的数组,将要添加的从前往后添加到新的数组。
  • 分类加载的时机取决于主类是否是懒加载和分类本身是否是懒加载。文中3.1都是非懒加载,会通过+load去实现loadAllCategories;3.2分为2种情况,分类实现一个+load的时候,主类会被迫实现,直接读取编译时确定的data(),包含了主类和分类的数据;另一种情况是实现2个以上分类+load,它分2步操作,一个是loadAllCategories,另一步是实现主类添加分类attachCategories3.3只实现主类+load,分类不实现。走主类非懒加载流程。同时编译时的data()包含了分类的数据。3.4 都是懒加载的时候,第一次发送消息的时候,走类的懒加流程。读取daa()