OC底层原理探索之类的加载原理下

269 阅读5分钟

read_images-> readClass(名字和类对应上)-> realizeClassWithoutSwift(ro.rw.supCls.isa) -> methodizeClass -> prepareMethodLists(在类中写入方法名字.排序)

read_images

根据上一篇的readClass里的测试代码我们知道,mangleName可以得到这个类的名字,那么此时我们可以在read_images多处插上测试代码来逐一验证。


    const char *mangledName = cls->nonlazyMangledName();
    // 测试代码
    const char *PersonName = "Person";
    if (strcmp(mangledName, PersonName) == 0) {
        printf("%s - %s\n",__func__,mangledName);
    }

我们在log9和log10的地方分别加入上面代码验证 image.png 运行之后发现这两处的断点并没有被卡住。注意看黄色框1上面的注释Realize non-lazy classes (for +load methods and static instances),意思有load方法的话就会走这里。此时我们在Person类中实现load()方法,再运行,发现这卡住了断点。step over到了realizeClassWithoutSwift这个方法

realizeClassWithoutSwift

这个方法是不是很眼熟,没错,我们之前在探究方法的慢速查找来到过这里,方法链路 lookUpImpOrForward -> realizeAndInitializeIfNeeded_locked -> initializeAndLeaveLocked -> realizeClassMaybeSwiftAndLeaveLocked -> realizeClassMaybeSwiftMaybeRelock -> realizeClassWithoutSwift 言归正传,我们继续探索,验证是不是在该方法内实现了methodlist ro rw rwe image.png 所以我们能得出,只是有了Person的地址,但是方法列表、ro、rw、rwe等还么有赋值放进去,

auto ro = (const class_ro_t *)cls->data();

cls->data()类型是class_rw_t*这里强转成了class_ro_t *

      // Normal class. Allocate writeable class data. ro -> rw
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);

然后来到了这里,在这里把ro干净的内存赋值一份到了rw。下面两行代码也对应了isa继承链和superCls的走位图

supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

methodizeClass

realizeClassWithoutSwift -> methodizeClass在这个方法里面同样插入调试代码拦截Person信息,按照上述流程继续打印ro,发现baseMethodList还是为空 image.png 继续往下,我们发现了系统也调用了baseMethods() image.png

prepareMethodLists

来到prepareMethodLists修正选择器fixupMethodList

  for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            printf("未排序之前: %s - %p\n",name, meth.name());
            meth.setName(sel_registerNameNoLock(name, bundleCopy));
   }

image.png 此时这里的排序也对应的慢速查找的二分查找。在排好序之后,回到原来的methodizeClass的主流程,继续p一下ro发现还是没有内容 以上所有的探索都是在Person类实现了load的前提下,那么没有实现load的话我们的方法ro rw 在哪里赋值

懒加载类情况(未实现load方法)

我们把Person的load方法注释掉,同时我们也知道必然会走这个方法realizeClassWithoutSwift,我们在这里卡个断点倒退,在realizeClassWithoutSwift断点查看调用栈 image.png 此时我们可以得到一个结论

懒加载类情况:数据加载推迟到第一次消息的时候非懒加载类情况:map_images时记载所有类信息
lookUpImpOrForwardreadClass
realizeClassMaybeSwiftMaybeRelock_getObjc2NonlazyClassList
realizeClassWithoutSwiftrealizeClassWithoutSwift
methodizeClassmethodizeClass

methodizeClass 看注释跟分类有关,那么我们就在main函数中加一个分类,查看main.cpp

struct _category_t {
	const char *name; //分类名
	struct _class_t *cls; // 类
	const struct _method_list_t *instance_methods;
	const struct _method_list_t *class_methods;
	const struct _protocol_list_t *protocols;
	const struct _prop_list_t *properties;
};

instance_methods:分类没有元类,所以当前的方法都在这里面。

static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	2,
	{{(struct objc_selector *)"cate_instanceMethod1", "v16@0:8", (void *)_I_Person_LG_cate_instanceMethod1},
	{(struct objc_selector *)"cate_instanceMethod2", "v16@0:8", (void *)_I_Person_LG_cate_instanceMethod2}}
};

同时也注意到了分类里面的方法列表没有写入setget方法

rwe什么时候赋值

回到methodizeClass方法,我们知道分类的方法存在于rwe中,

auto rwe = rw->ext()

点击到了ext(),跟get_ro_or_rwe()相关,而get_ro_or_rwe()extAllocIfNeeded()

    class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
    }

    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));
        }
    }

全局搜索extAllocIfNeeded定位到了这个方法attachCategories看注释将categories类别中的方法列表、属性和协议附加到类中说明我们查找的地方没有错

attachCategories

发现有两处调用了这个方法 1.realizeClassWithoutSwift -> methodizeClass -> attachToClass-> attachCategories 2.load_categories_nolock ->attachCategories -> extAllocIfNeededattachCategories里面我们找到了一个分类的插入算法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];
            validate();
        } 
        else {
            // 1 list -> many lists
            Ptr<List> oldList = list;
            uint32_t oldCount = oldList ? 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];
            validate();
        }
    }

我们知道类的加载跟load有关系,那么分类的加载是否跟load有关系

分类 + 类搭配加载

列出下面所有的4种情况分析

分类load方法实现主类load方法实现加载顺序
_read_images ->realizeClassWithoutSwift->methodizeClass->load_categories_nolock ->attachCategories
没有_read_images ->realizeClassWithoutSwift->methodizeClass(无attachCategories)
有(1个)没有_read_images ->realizeClassWithoutSwift->methodizeClass(无attachCategories)
有(>1)没有load_images -> prepare_load_methods -> realizeClassWithoutSwift -> methodizeClass -> attachCategories
没有没有什么都没有

我们根据上面四种情况来分析,分类的数据是什么时候加载进来的,补充同时如果分类没有内容的话不会加载进内存。

分类数据加载时机

  1. 两个都有情况下:在**realizeClassWithoutSwift**方法控制台打印 image.png逐一打印发现没有分类的方法,都是主类的方法。我们知道还会到**attachCategories**,在这里继续打印验证 image.png 发现在这里已经把分类的方法给添加上去了。继续往下走,我们看到了上面写的分类的插入算法attachList
 rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);

image.png 由上面我们知道第一个参数是一个指针的指针,也就是一个二维指针

  1. **attachCategories**分类怎么加载 不管是分类还是主类只有一个地方实现了load方法,还是在来到了read_images的非懒加载里面,说明主类有的情况下分类被迫营业。此时我们来到**realizeClassWithoutSwift**方法按照上面的打印输出,发现此时的count=13说明这种情况之下,分类已经加载到主类里面去了。count即为分类的数量直接从mach-o里面读取。 **_read_images** ->**realizeClassWithoutSwift**->**methodizeClass**-load_images->loadAllCategories -> load_categories_nolock -> attachCategories image.png

  2. 情况跟2相同

  3. 主类实现,多个分类(超过1个实现load)的情况下,发现流程有点不同 _read_images ->realizeClassWithoutSwift-> methodizeClass -> load_images -> prepare_load_methods -> realizeClassWithoutSwift -> methodizeClass -> attachCategorie

  4. 两个都没有实现的情况下,分类的加载会推迟到第一次消息发送lookUpImpOrForward的时候初始化data