引入
在类的加载中,我们知道类的加载是在realizeClassWithoutSwift
函数中实现的,但是我们还有一些❓,
-
❓
auto ro = (const class_ro_t *)cls->data();
里面的data()又是从哪里获取的呢? -
❓我们知道有ro,rw,rwe,为什么rw要从ro中复制,为什么会有rwe,rwe又是什么时候赋值的?
-
❓分类的本质是什么,分类的加载流程是怎么样子的,为什么要避免load方法的过度使用
ro、rw、rwe
在realizeClassWithoutSwift
中看到auto ro = (const class_ro_t *)cls->data();
ro是从cls->data()中获取的,打印一下
看到里面是有值的,cls->data()中data()的定义如下:
class_rw_t *data() const {
return bits.data();
}
//点击bits.data()
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
cls->data(),是从macho中读取数据,最后返回的是一个指针类型,只要接收的数据类型中和之前类型里面的数据结构是一一对应的,就会进行赋值操作。所以auto ro = (const class_ro_t *)cls->data();
后面加个断点,就可以打印出来具体内容信息。
- ro ro属于clean memory,在编译即确定的内存空间,是从disk中读取的,只读,加载后不会改变内容的空间
- rw rw属于dirty memory,rw属于运行时结构,可读可写,可以向类中添加属性、方法等。在运行时会改变的内存。
- rwe rwe相当于类的额外信息,因为在实际使用过程中,只有很少的类会真正改变他们的内容,所以为了避免资源的消耗就有了rwe 运行时,如果需要动态向类中添加方法协议等,会创建rwe,并将ro的数据优先attache到rwe中,在读取时会优先返回rwe的数据,如果rwe没有被初始化,则返回ro的数据。 rw中包含ro和rwe,其中rw是dirty memory,ro是clean memory;为了让dirty memory占用更少的空间,把rw中可变的部分抽取出来为rwe; clean memory越多越好,dirty memory越少越好,因为iOS系统底层虚拟内存机制的原因,内存不足时会把一部分内存回收掉,后面需要再次使用时从硬盘中加载出来即swap机制,clean memory是可以从硬盘中重新加载出来的内存,iOS的macho文件动态库属于此类型;dirty memory是运行时产生的数据,这部分数据不能从硬盘中重新加载,所以必须一直占据内存,当系统物理内存紧张的时候,会回收掉clean memory内存,如果dirty memory过大,则直接会被回收掉,所以clean memory越多越好,dirty memory越少越好; 苹果对ro、rw、rwe进行这么细致的划分都是为了能够更好的区别dirty memory和 clean memory;
那么什么时候有rwe呢,从上面rwe的解释中我们知道,当我们添加分类的时候会有rwe,那么我们看下分类的数据是如何加载的?
分类的本质
示例代码:
//main
@interface Person (Stu)
+(void)sayHappy;
@end
@implementation Person (Stu)
+(void)sayHappy {
NSLog(@"hahahhaha");
}
@end
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
[Person sayHappy];
}
return 0;
}
@end
我们写了一个Person的分类Person+Stu.h,然后来进行调用。使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
生成main.cpp
我们看下_category_t
结构
里面有name、cls、instance_methods、class_methods、Protocols、properties。
我们说类的内部实现是不区分类方法和实例方法的,为什么分类中都列出来了呢,其实是因为分类没有元类,所以这个地方都列了出来。
我们给它添加两个属性:
@property (nonatomic, copy) NSString *stuName;
@property (nonatomic, copy) NSString *gender;
在查看main.cpp
我们看到没有对应的setter方法和getter方法实现。也没有带_的成员变量 所以一般不给分类添加属性,添加了也取不到,我们可以使用runtime关联对象的形式来给分类添加属性。
在methodizeClass
中看到源码if (rwe) rwe->methods.attachLists(&list, 1);
,那么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的时候创建的,全局搜索看哪些地方有用到,我们看到在一些attachCategories
、 demangledName
、class_setVersion
、addMethods_finish
、class_addProtocol
、_class_addProperty
、objc_duplicateClass
动态处理的一些函数中用到。
attachCategories
方法猜测与分类有关我们看下哪里有调用,可以看到在attachToClass
和load_categories_nolock
中有调用。
那么我们来验证一下,创建一个分类,在main函数中调用
部分示例代码
//LGPerson.h
@interface LGPerson: NSObject
@property (nonatomic, copy) NSString *name;
- (void)saySomething;
@end
//LGPerson.m
@implementation LGPerson
+ (void)load{}
- (void)saySomething{
NSLog(@"%s", __func__ );
}
//LGPerson+LGA.h
@interface LGPerson (LGA)
@property (nonatomic, copy) NSString *cateA_name;
@property (nonatomic, assign) int *cateA_age;
- (void)saySomething;
- (void)cateA_instanceMethod1;
- (void)cateA_instanceMethod2;
+ (void)cateA_classMethod1;
+ (void)cateA_classMethod2;
@end
//LGPerson+LGA.m
@implementation LGPerson (LGA)
+ (void)load{}
- (void)saySomething{
NSLog(@"%s", __func__ );
}
- (void)cateA_instanceMethod1{
NSLog(@"%s", __func__ );
}
- (void)cateA_instanceMethod2{
NSLog(@"%s", __func__ );
}
+ (void)cateA_classMethod1{
NSLog(@"%s", __func__ );
}
+ (void)cateA_classMethod2{
NSLog(@"%s", __func__);
}
@end
//main
LGPerson * person = [LGPerson alloc];
[person saySomething];
[LGPerson cateA_classMethod1];
分类中有load方法,类中也有load方法
我们以方法为例,探究这种情况是如何加载的。
在类的实现realizeClassWithoutSwift
打一个断点
打印一下方法,一共有四个方法,都是主类的,没有分类的,说明分类的还没有加载出来,
方法调用路径依次是:
_read_images非懒加载类
->realizeClassWithoutSwift
->methodizeClass
->attachToClass
->load_categories_nolock
->attachCategories
->attachLists
load_categories_nolock
static void load_categories_nolock(header_info *hi) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
size_t count;
auto processCatlist = [&](category_t * const *catlist) {
for (unsigned i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
locstamped_category_t lc{cat, hi};
...
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
if (cls->isRealized()) {
//实例为非懒加载类,走这里
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
} else {
objc::unattachedCategories.addForClass(lc, cls);
}
}
...
}
processCatlist(hi->catlist(&count));
processCatlist(hi->catlist2(&count));
auto processCatlist = [&](category_t * const *catlist) {}
是一个block
由 processCatlist(hi->catlist(&count));
的地方传入
可以看到category_t
的结构和上面我们分析的对应,在c++分析中name还没有确定,所以显示的是Person,说明分类的值是在运行时赋值的。
attachCategories
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
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;
...
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) {//ATTACH_BUFSIZ = 64, 不走这里
//方法排序
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__ );
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
//走这里
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
...
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__ );
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
...
}
...
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
我们打印下mlist 和mlists
ATTACH_BUFSIZ - ++mcount = 63
把本类的地址方法列表的地址放在了最后一个元素。
mlists + ATTACH_BUFSIZ - mcount
输出它的值,看到是一个二级指针,(method_list_t **) $1 = 0x00007ffeefbf5ae8
attachLists
我们看下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
array()->count = newCount;
//将oldlist放在array()->lists后面的位置
if (oldList) array()->lists[addedCount] = oldList;
for (unsigned i = 0; i < addedCount; i++)
//为新添加的list赋值
array()->lists[i] = addedLists[i];
validate();
}
}
第一次是本类的加载,我们这个方法会走下面的// 1 list -> many lists
在array()->lists[i] = addedLists[i];
打印一个断点,打印下addedLists[i]
断点向后移动,打印array->list[]中的值
我们看到array->list[i]中存的都是ptr,而ptr是一个指向method_list_t的指针。
从这一部分代码看出来,这个array的长度为2,里面放的是指向method_list_t的指针,
我们在多写一个分类,添加如下代码
//#import "LGPerson+LGB.h"
@interface LGPerson (LGB)
- (void)sayNothing;
@end
//#import "LGPerson+LGB.m"
@implementation LGPerson (LGB)
- (void)sayNothing {
NSLog(@"sdfsf");
}
@end
//main
[person sayNothing];
打下入上面断点,打印newArray->lists[i]的值,是先把之前的主类和分类LGA的赋值在后面,我们打印下,[1]和[2]中的值,看下是否为主类和LGA中的方法。
然后再第一个位置,插入最后编译的分类,如下图我们打印LGB的方法 加载之后释放掉free(array());下次再有分类,则进入到// 1 list -> many lists,依次循环。
分类中没有load方法,类中有load方法
走如下方法调用
_read_images非懒加载类
->realizeClassWithoutSwift
->methodizeClass
->attachToClass
它没有走attachCategories
,那么分类的方法时什么时候加载的呢,我们在realizeClassWithoutSwift加个断点,打印一下ro的信息(为了简单,我们这个时候代码中去掉了LGB),由下面的打印知道,这个时候已经有了分类的信息,则这个时候分类的信息是在data()里面获取的。
分类中有load方法,类中没有load方法
走如下方法调用
_read_images非懒加载类
->realizeClassWithoutSwift
->methodizeClass
->attachToClass
同理验证分类的方法也是在data()中获取的。
分类中没有load方法,类中没有load方法
懒加载类
在第一次消息转发的时候,加载类和分类的方法,也是从data()中获取的。
如果有多个分类,分类中有的有load方法有的没有呢
主类load方法没有实现
_read_images非懒加载类
->realizeClassWithoutSwift
->methodizeClass
->attachToClass
分类方法从data()中获取
主类有load方法
_read_images非懒加载类
->realizeClassWithoutSwift
->load_categories_nolock
->attachCategories
分类方法从attachCategories
中动态添加
综上:尽量避免使用过多的load方法