上一篇我们探索了类的加载流程等一系列方法
以及懒加载类和非懒加载类
这节课我们来探索下分类的加载流程
分类的本质
首先现在main.m
中添加LGPerson
的分类
@interface LGPerson (LG)
@property (nonatomic, copy) NSString *cateA_name;
@property (nonatomic, assign) int *cateA_age;
- (void)saySomething;
- (void)cateA_instanceMethod1;
- (void)cateA_instanceMethod2;
@end
@implementation LGPerson (LGA)
- (void)saySomething{
NSLog(@"%s",__func__);
}
- (void)cateA_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)cateA_instanceMethod2{
NSLog(@"%s",__func__);
}
@end
clang -rewrite-objc main.m -o main.cpp
得到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;//属性
};
static struct _category_t _OBJC_$_CATEGORY_LGPerson_$_LGA __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"LGPerson",
0, // &OBJC_CLASS_$_LGPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_LGPerson_$_LGA,
0,
0,
0,
};
- 分类被翻译成
c++
文件后,可以看出_category_t
是一个结构体 - 分类没有
元类
的概念,所以instance_methods
存储实例方法
,class_methods
存储类方法
- 我们可以找到
实力方法和类方法
的实现代码,但是没有属性的set和get
方法,所以分类
不会自动生成方法 - 在结构体中我们没有找到
ivar
的列表,所以分类中不能声明成员变量
分类的加载
回到上节课所讲的methodizeClass
方法
**********************************************************************/
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, "LGPerson") == 0)
{
if (!isMeta) {
printf("%s -LGPerson....\n",__func__);
}
}
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
//将属性列表、方法列表、协议列表等贴到rwe中
// 将ro中的方法列表加入到rwe中
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
//将属性添加到rwe中
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
//将协议添加到rwe中
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
}
- 发现
方法列表、属性列表、协议列表
的加载都有rwe
的条件判断,而rwe通过rw->ext()
来赋值的,接下来进入rwe
的初始化方法
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
是rwe的初始化方法,他的作用是来判断,如果rwe
存在,则直接返回,不存在,则需要初始化开辟一个空间
反推
全局搜索extAllocIfNeeded
的调用,发现在attachCategories
函数中有调用
然后全局搜索attachCategories
函数,发现主要集中在attachToClass 和 load_categories_nolock
两个方法中调用
void attachToClass(Class cls, Class previously, int flags)
{
......
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);
}
}
......
};
static void load_categories_nolock(header_info *hi) {
......
if (cls->isStubClass()) {
if (cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties ||
cat->classMethods ||
cat->protocols ||
(hasClassProperties && cat->_classProperties))
{
objc::unattachedCategories.addForClass(lc, cls);
}
} else {
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
if (cls->isRealized()) {
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
} else {
objc::unattachedCategories.addForClass(lc, cls);
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
if (cls->ISA()->isRealized()) {
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
} else {
objc::unattachedCategories.addForClass(lc, cls->ISA());
}
}
}
}
};
}
......
接下来我们反推attachToClass
方法,全局搜索该方法,发现该方法仅在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);
......
反推load_categories_nolock
方法,全局搜索,发现在_read_images
中调用
走到这一步我们有两条主线
-
_read_images
->load_categories_nolock
->attachCategories
-
realizeClassWithoutSwift
->methodizeClass
->attachToClass
->attachCategories
分类与主类加载的4种情况
首先我们现在_read_images、realizeClassWithoutSwift 、load_categories_nolock、methodizeClass、attachToClass、attachCategories
这6个方法中分别写上判断并打上断点
const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, "LGPerson") == 0)
{
if (!isMeta) {
printf("%s LGPerson....\n",__func__);
}
}
接下来我们来实现一个分类
在
main
中打上断点image.png
分类+主类同时实现 load方法
主类 分类同时实现load根据控制台和调用栈的打印 我们得出的流程
-
_read_imags
->realizeClassWithoutSwift
->methodizeClass
->attachToClass
函数走完后 -
load_images
->loadAllCategories
->load_categories_nolock
->attachCategories
分类实现load 主类不实现
image.png在这种情况下attachCategories
没有被调用,只是_read_images
相关函数的调用。
流程:_read_imags
->realizeClassWithoutSwift
->methodizeClass
-> attachToClass
分类不实现load 主类实现load
image.png这种情况的结果,和上一个一样,attachCategories
同样没被调用
流程:_read_imags
->realizeClassWithoutSwift
->methodizeClass
-> attachToClass
主类 分类 都不实现
image.png由此可知在程序启动的时候,类并没有加载,当初始化类的时候,通过
方法的慢速查找
,一步步的走完流程流程 :
lookUpImpOrForward
->realizeClassWithoutSwift
->methodizeClass
-> attachToClass
流程加载跟踪
load_categories_nolock
从上述流程中得知,只有第一种情况分类主类都实现load
的情况下才走了attachCategories
,接下来我们来探索load_categories_nolock
由上图我们看到,count为1,代表
分类数目
,打印cat
,也就是我们分类的信息,我们可以看到分类名是LGA,instanceMethod和classMethods
是有值的。
image.png
·lc
是一个结构体类型,里面存储cat和hi
,hi
是符号表信息
接下来断点走到attachCategories
方法
attachCategories
断点进入到attachCategories
方法
我们看到
cat_count等于1
代表1个分类
,而mlist中的count等于2
代表该分类中有两个方法。mlist
是分类方法的结构体
接下来断点继续往下走
image.png image.png
ATTACH_BUFSIZ等于64
-
mcount初始化为0
这里进行了++
操作 -
mlists[ATTACH_BUFSIZ - ++mcount] = mlist
这里是吧mlist
放到mlists的第63个下标
,也就是倒序放入分类,另外从这里得知,一个主类的最多有64
个分类
断点继续往下走,走到prepareMethodLists
进行方法排序
最终走到
attachLists
方法image.png
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();
}
}
image.png
- 由打印可知,
oldList
中存放着主类的方法
,count==1是主类的方法个数
-
oldList
中存放的是method_list_t *
类型的 - 由打印可知,
addedLists
存放的也是method_list_t *
类型
-newCount
等于2,有代码可知newArray
,先将oldList
放入到最后的位置
,再将addedLists 的数据遍历添加至主类前面
,所以说这也是我们常说的主类与分类方法名相同时,分类覆盖主类
,但是这里并不是真正的覆盖
,只是将分类方法 放到前面
四种情况的分类数据加载
分类 主类均有load
由上面分析可知,分类 主类均有load
情况,是在load_imags
后全部的动态加载,这里不做更多讲解
分类实现load 主类不实现
在realizeClassWithoutSwift
打上断点
- 这个时候我们发现在
ro->baseMethods()中
,里面有3个方法
结论: 由此我们得出分类实现load 主类不实现
情况下,分类的加载在编译期
就已经处理好了,说明是通过data()加载的
分类不实现load 主类实现
这种情况和上述情况一样,分类也是通过data()加载的
主类 分类均不实现
通过方法的慢速查找流程 lookUpImpOrForward
->realizeClassWithoutSwift
,最终 分类的数据也是通过data()
加载的
多个分类有load 主类有
在load_categories_nolock
添加断点
发现
count==3
由此得出其 流程和分类主类均实现的加载流程是相同的
网友评论