iOS底层探索之类的加载(三): attachCategorie
1.回顾
在上篇博客中,已经对类的加载有了一定的了解,分类的加载也定位到了attachCategories
方法中,那么本篇博文将对分类进行探索分析!
iOS底层探索之类的加载(二): realizeClassWithoutSwift分析
2. 分类分析
2.1 分类加载路线
在上篇博客中通过反推,已经确定了分类加载的两条路线,分别是:
methodizeClass --> attachToClass --> attachCategories
load_images --> loadAllCategories --> load_categories_nolock --> attachCategories
- realizeClassWithoutSwift
methodizeClass
是在realizeClassWithoutSwift
中调用:
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
。。。。。。。。省略。。。。。。
// Attach categories
methodizeClass(cls, previously);
return cls;
}
- methodizeClass
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();
。。。。。。。。省略。。。。。。
// 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);
。。。。。。。。省略。。。。。。
}
Attach categories通过搜索
realizeClassWithoutSwift
的调用,发现previously
这个参数是nil
,也就是methodizeClass
方法的参数previously
也是等于nil
,也就是如下图中,代码if
不会执行
那么为什么
previously
是nil
呢?那这个参数的意义是什么呢?应该是苹果工程师便于调式代码使用的,也就是备用的参数,便于动态化进行调式。
2.2 attachCategories
通过注释可以知道大概意思是:将方法列表、属性和协议从分类附加到类中。假设cats
中的categories
全部加载完毕,并按加载顺序排序好了,首先是最旧的类别最先开始附加到类中。
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.
* This uses a little stack, and avoids 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();
const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, "LGPerson") == 0)
{
if (!isMeta) {
printf("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) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
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__);
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
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
- 然后是判断是否来自
fromBundle
,是否是元类
,进行标记,对rwe
进行初始化。
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
auto rwe = cls->data()->extAllocIfNeeded();
- 通过
for
循环对分类的方法、属性、协议进行处理,将相关信息attach
到类中。获取分类方法列表,如果当前类是元类则获取类方法列表,否则获取实例方法列表:
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
methodsForMeta
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
对分类列表进行循环处理,在插入数据时,以倒序( mlists[ATTACH_BUFSIZ - ++mcount] = mlist
)的方式插入。需要注意的是mLists
是一个二维数组,测试打印结果如下:
(lldb) p mlist
(method_list_t *) $4 = 0x00007fff80b44aa8
(lldb) p mlists
(method_list_t *[64]) $5 = {
[0] = 0x00007fff80849050
[1] = 0x00007fff80848a50
[2] = 0x00007fff205190b9
[3] = 0x00007fff203acfca
[4] = nil
[5] = 0x0000000000001e00
............
[58] = 0x00000001003660f0
[59] = 0x0000000100366140
[60] = 0x00007ffeefbf5b50
[61] = 0x00000001002f27fa
[62] = 0x00007ffeefbf5b50
[63] = 0x00007fff80b44aa8
}
- 从打印结果可以验证,最大存储空间是是
64
。 - 把本类的方法列表的地址放在了数组中的最后一个元素,对比地址是一模一样
0x00007fff80b44aa8
。
- 在对方法、属性、协议的处理完之后,就会将相关的集合数据插入到
rwe
中:
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
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);
需要我们重点注意的是,存方法的
mlist
是一个二维数组。分类方法的排序,也只是针对各个自己分类内的方法进行分别排序,并不会将所有分类的方法都全部集中放到一个集合中进行排序。
2.3 rew
处理
在上一篇博客中,已经提到了rwe
的赋值是来源于rw->ext()
代码如下:
auto rwe = rw->ext();
ext()
是属于class_rw_t
结构体中的方法
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() {
//获取rwe
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 {
//创建rwe
return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
class_rw_t
中还有rwe
创建和获取方法。
通过在objc
源码里面跟踪extAllocIfNeeded
方法,发现会调用extAlloc
方法进行初始化。流程中会将ro
的数据优先插入到rwe
中:
extAlloc
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>();
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;
}
从上面extAlloc
的方法中可以看出,各个分支语句对方法、属性、协议的处理都会调用attachLists
方法,那么现在就去分析下attachLists
。
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];
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();
}
}
attachLists通过代码的断点,可以看出
addedLists
是一个指针,指向一个二维数组的指针,如下图所示:
- 分类初次进入,会进行
array()
的初始化,同时设置数组的大小,即为原类
的列表数量添加分类
的列表数量。同时先将类的list
放到最后一个位置,这时候一维数组变为二维。
// 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();
通再过一个for
循环把将分类
对应的list
添加到array()
中去
- 下次再次进入时,由于
array()
已经初始化,所以会走到if
分支中。
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();
}
验证一下当前array()
的数据顺序,和第一次插入时是一致的,类list
在后,分类list
在的前面,最后由二维还是变为了二维数组。
此时
malloc
会开辟一个新的newArray
重新初始化,将原数组的数据,进行顺序不变的情况下,插入到新的array
中,同时将新增的分类list
插入到第一个位置处。
- 在前面
rwe
的创建过程中,已经进行了一维数组的创建,那么这个时候再调用attachLists
时,会将ro
的数据优先放入到rwe
对应的一维数组中去。
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
3. 类和分类的加载
懒加载类与⾮懒加载类: 是指当前类是否实现load
⽅法。
实现了load
方法就是非懒加载的类。
建立分类调试验证结果如下:
3.1 分类和类都实现load方法
分类和类都实现load
方法,就是非懒加载类和非懒加载分类情况。
- 主类调用路径依次是:
map_images ->_read_images(非懒加载类)->realizeClassWithoutSwift->methodizeClass
打印方法都是主类的,没有分类的,说明此时分类的还没有加载出来
主类初始化
调用
map_images
方法就是对类开始初始化流程,methodizeClass
会对ro
中的方法进行排序,而rwe
还未创建。
- 分类:
load_images->loadAllCategories->load_categories_nolock->attachCategories
分类初始化 - 过掉断点,再断点在
attachCategories
方法里面,打印分类方法如下
分类方法
3.2 主类实现load,分类没有load
主类实现load
,分类没有load
,这属于非懒加载类和懒加载分类情况,通过断点调试,它没有走attachCategories
,那么分类的方法时什么时候加载的呢,我们在realizeClassWithoutSwift
加个断点,打印一下ro
的信息。
这个时候已经有了分类的信息,由此得出结论:这个时候分类的信息是在
data()
里面获取的。方法调用路径依次是:
map_images->map_images_nolock->_read_images(非懒加载类)->realizeClassWithoutSwift->methodizeClass->attachToClass
。
3.3 主类没有load,分类有实现load
主类没有load
,分类有实现load
,就是懒加载类和非懒加载分类情况。
调试控制台打印
ro
方法列表,可以看到分类的方法在前面
,也就说,在插入方法的时候,分类的方法插在主类的前面。
方法调用路径依次是:
map_images->map_images_nolock->_read_images(非懒加载类)->realizeClassWithoutSwift->methodizeClass->attachToClass
,并且不会调用attachCategories
方法,同理验证分类的方法也是在data()
中获取的,分类中的方法在编译阶段已添加到data()中。
3.4 主类、分类都没有实现load
主类、分类都没有实现load
,就是懒加载类和懒加载分类的情况。
通过验证:懒加载类在第一次消息发送的时候,加载类和分类的方法,也是从data()
中获取的。
方法调用路径依次是:
lookUpImpOrForward->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift
不会调用attachCategories
方法,懒加载类,第一次消息发送时初始化,并且分类中的方法自动添加到
data()
中
3.5 有多个分类实现了load方法有的没有实现
主类load
方法没有实现,但是多个分类中,有的实现了load
方法,有的没有实现load
方法。
- 主类
load
方法没有实现,执行路径是:
_read_images(非懒加载类)->realizeClassWithoutSwift->methodizeClass->attachToClass
,分类方法从data()
中获取` - 主类有
load
方法,执行路径是:
_read_images(非懒加载类)->realizeClassWithoutSwift->load_categories_nolock->attachCategories
,
分类方法从attachCategories
中动态添加。
4. 总结
-
懒加载的类
和非懒加载类
的区别是,是否实现了+load
方法 - 类和分类的方法是在各自的
list
进行排序的 - 尽量避免使用过多的
load
方法,会消耗太多加载时间 -
addedLists
是一个指针,指向一个二维数组的指针 -
rwe
并不是每个类都有,是在runtime
运行时向类添加方法、分类、协议,以及设置版本时,才会对rwe
进行操作