继续上一篇探索下分类的加载流程。上一篇我们探索类的加载其中methodizeClass
方法有关于分类的加载。
1.分类加载推导
继续查看attachToClass
方法,
发现添加分类的方法attachCategories
,我们用反推的方法判断哪里调用了它,发现有2处调用了,一处就是我们上面attachToClass(第一处)
中调用。
- 再看
load_categories_nolock(第二处)
调用
继续查找load_categories_nolock
,有2处调用
第一处在loadAllCategories
调用。继续搜索loadAllCategories
得到
查看第二处:
第二处在之前熟悉的read_images
调用。其中只有当didInitialAttachCategories = true
的时候才会进入。而didInitialAttachCategories
默认是false
,在上面loadAllCategories
方法中会设置didInitialAttachCategories
为true
。
根据分析的流程可以得出添加分类的大概流程,如下:
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);
.....
}
previously
是methodizeClass
方法带入进来的,它来自realizeClassWithoutSwift()
全局搜索
发现调用处穿的都是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
。
取出mlist
赋值给mlists
,从后往前进行赋值
。
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中。
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);//大小
}
};
many lists -> many lists
先计算出之前的二维数组大小,新的数组大小。根据新的大小开辟新的array_t
数组,之后把老的array_t
数组的值从后往前塞进新的array_t
数组。0 lists -> 1 list
二维数组为空
,添加的数量为1
.第一次进来。addedLists只有一个值直接取0位置的值
,赋值给list。1 list -> many lists
取出之前的二维数组只有一个值,之前看过array_t的数据格式,知道Ptr<List> lists[0]
。计算出新添加的二维数组大小,根据新的大小创建新的array_t
数组。先吧就的值赋值到新数组的最后一位
,因为只有一个不用循环;之后循环从初始位置赋值
。
流程如下:
3. 主类和分类加载时机
我们之前探究到类的加载分为懒加载
和非懒加载
,那么按照节约内存的情况,分类也应该类似。但是我们分析了分类加载依赖于类,于是就有4种情况,我们一一分析。
3.1 主类和分类都实现load方法
我们在attachCategories
中添加断点,进入断点后,我们打印bt信息。
得出流程load_images
-->loadAllCategories
-->load_categories_nolock
-->attachCategories
3.2主类不实现+load,分类实现+load
在main函数前打个断点防止进入懒加载流程
发现没有进入attachCategories
,明明已经分类实现了+load,应该走非懒加载方法进入attachCategories
但是却没有。那么到底分类的信息添加到类没有?带着疑问我在
说明这个在实现类的时候也读取了分类的数据。主类没有进行+load方法调用,也没有发送消息进入慢速查找流程实现类。那么如何进入realizeClassWithoutSwift
?上一步
这段代码是实现非惰性类
也就是+load
方法实现的。是prepare_load_methods
方法回调。实现分类+load方法时候会把主类也实现,读取data()包含了分类和主类的数据,而数据在编译的时候就确定了。得出流程 _read_images
-->readClass
-->realizeClassWithoutSwift
3.2.1 主类不实现+load,多个分类,只有一个分类实现+load;
新建分类LGB,LGC,
不实现+load方法,继续上面分析。
结果和上面只有一个+load一样。流程_read_images
-->readClass
-->realizeClassWithoutSwift
3.2.2 主类不实现+load,分类实现2个以上+load;
根据自定义断点可知。先进入
之后进入load_categories_nolock
,加载分类。
之后进入prepare_load_methods
查找分类中实现+load
的分类列表,并实现主类realizeClassWithoutSwift
在主类实现好进入methodizeClass
,继续下面的流程,打印堆栈信息
流程如下:load_images
-->loadAllCategories
(加载分类)-->load_categories_nolock
(处理分类数据)-->prepare_load_methods
(准备)-->realizeClassWithoutSwift
(实现类)-->methodizeClass
-->attachToClass
-->attachCategories
(添加分类)
3.3 主类实现+load,分类不实现+load
分类都不实现+load方法,只有主类实现+load。主类走我们之前探索类的时候实现类
代码中的非懒加载代码块
打印一下堆栈的信息,整理下流程
流程如下:map_images
-->map_images_nolock
-->_read_images
-->realizeClassWithoutSwift
-->methodizeClass
在编译的时候确定了类和分类的data
3.4 主类和分类不实现+load
当第一次调用类发送消息的时候,不管调用的是类的方法还是分类的方法,走类的懒加载流程。此时流程:lookUpImpOrForward
-->realizeClassMaybeSwiftMaybeRelock
-->realizeClassWithoutSwift
4.总结
- 分类加载流程分析可以通过
反推
的方法,去查找它的流程。 - 分类添加到主类的时候会把数据写入rwe,判断
是否已经存
在rwe,不存在就创建一个同时把主类的数据也写入。分类的数据写入通过attachList
写入属性,方法列表,协议等。写入过程是根据添加表的个数加之前添加的个数开辟新的数组,之前的数组值从后往前
插入新的数组,将要添加的从前往后
添加到新的数组。 - 分类加载的时机取决于主类是否是懒加载和分类本身是否是懒加载。文中
3.1
都是非懒加载,会通过+load
去实现loadAllCategories
;3.2
分为2种情况,分类实现一个+load
的时候,主类会被迫实现
,直接读取编译时确定的data()
,包含了主类和分类的数据;另一种情况是实现2个以上分类+load
,它分2步操作,一个是loadAllCategories
,另一步是实现主类添加分类attachCategories
;3.3
只实现主类+load
,分类不实现。走主类非懒加载流程。同时编译时的data()包含了分类的数据。3.4
都是懒加载的时候,第一次发送消息的时候,走类的懒加流程。读取daa()
。