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的地方分别加入上面代码验证
运行之后发现这两处的断点并没有被卡住。注意看黄色框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
所以我们能得出,只是有了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还是为空
继续往下,我们发现了系统也调用了
baseMethods()
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));
}
此时这里的排序也对应的慢速查找的二分查找。在排好序之后,回到原来的
methodizeClass的主流程,继续p一下ro发现还是没有内容
以上所有的探索都是在Person类实现了load的前提下,那么没有实现load的话我们的方法ro rw 在哪里赋值
懒加载类情况(未实现load方法)
我们把Person的load方法注释掉,同时我们也知道必然会走这个方法realizeClassWithoutSwift,我们在这里卡个断点倒退,在realizeClassWithoutSwift断点查看调用栈
此时我们可以得到一个结论
| 懒加载类情况:数据加载推迟到第一次消息的时候 | 非懒加载类情况:map_images时记载所有类信息 |
|---|---|
lookUpImpOrForward | readClass |
realizeClassMaybeSwiftMaybeRelock | _getObjc2NonlazyClassList |
realizeClassWithoutSwift | realizeClassWithoutSwift |
methodizeClass | methodizeClass |
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}}
};
同时也注意到了分类里面的方法列表没有写入set和get方法
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 -> extAllocIfNeeded
在attachCategories里面我们找到了一个分类的插入算法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 |
| 没有 | 没有 | 什么都没有 |
我们根据上面四种情况来分析,分类的数据是什么时候加载进来的,补充同时如果分类没有内容的话不会加载进内存。
分类数据加载时机
- 两个都有情况下:在
**realizeClassWithoutSwift**方法控制台打印逐一打印发现没有分类的方法,都是主类的方法。我们知道还会到
**attachCategories**,在这里继续打印验证发现在这里已经把分类的方法给添加上去了。继续往下走,我们看到了上面写的分类的插入算法
attachList
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
由上面我们知道第一个参数是一个指针的指针,也就是一个二维指针
-
无
**attachCategories**分类怎么加载 不管是分类还是主类只有一个地方实现了load方法,还是在来到了read_images的非懒加载里面,说明主类有的情况下分类被迫营业。此时我们来到**realizeClassWithoutSwift**方法按照上面的打印输出,发现此时的count=13说明这种情况之下,分类已经加载到主类里面去了。count即为分类的数量直接从mach-o里面读取。**_read_images**->**realizeClassWithoutSwift**->**methodizeClass**-load_images->loadAllCategories->load_categories_nolock->attachCategories -
情况跟2相同
-
主类实现,多个分类(超过1个实现load)的情况下,发现流程有点不同
_read_images->realizeClassWithoutSwift->methodizeClass->load_images->prepare_load_methods->realizeClassWithoutSwift->methodizeClass->attachCategorie -
两个都没有实现的情况下,分类的加载会推迟到第一次消息发送
lookUpImpOrForward的时候初始化data