前言
上一篇底层原理之类的加载我们通过read_images探索了类的加载过程,懒加载类在方法第一次调用时realizeClassWithoutSwift这个方法对类、元类以及父类进行了初始化,而非懒加载类即实现load()方法的类在read_images时就通过realizeClassWithoutSwift方法进行了初始化,主要是初始化了ro和rw,rwe是Nil,同时read_images也会处理分类,下面就分析下分类。
1.0 认识分类
我们在main方法里写个分类的demo,clang编译一下看下分类的结构
@interface GYPerson : NSObject
@end
@implementation GYPerson
@end
@interface GYPerson (GY)
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;
-(void)say1;
-(void)say2;
@end
@implementation GYPerson (GY)
-(void)say1{};
-(void)say2{};
@end
clang:(clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m)编译后部分代码如下:
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 /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_GYPerson_$_GY __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"say1", "v16@0:8", (void *)_I_GYPerson_GY_say1},
{(struct objc_selector *)"say2", "v16@0:8", (void *)_I_GYPerson_GY_say2}}
};
分析:
分类本质上是一个_category_t结构体,而且里面不仅有instance_methods对象方法还有class_methods类方法,这说明分类没有元类,因为类方法应该在元类中- 我们看到有say1、say2方法,但是却
没有看到分类中属性的set、get方法,说明分类不会主动生成这两个方法,需要用我们所知道的关联对象的方式实现,这个后面的文章会分析。
2.0 ro、rw、rwe由来
我们知道类中有ro、rw以及rwe,那么为什么会有呢?
ro:干净的内存(clean Memory),从磁盘里也就是沙盒中读取,是不会变的,是高效的,比如类的名字、父类rw:脏内存(Dirty Memory),是从ro拷贝过来的,很昂贵,是动态变化的比如方法、属性等,所以需要优化,尽可能的把Dirty Memory转化成Clean Memory。rwe:rw的优化,当我们的方法、属性动态发生变化时就创建了rwe,为什么创建?因为大多数情况下类的方法、属性等都不会发生变化都不会创建rwe,如果一旦变化就放进rw中就太浪费资源了,所以苹果就对rw进行了扩展,用rwe专门存放动态发生变化的内存。
3.0 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();
//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));
}
}
分析:我们看到如果调用rwe的extAllocIfNeeded()方法就会初始化rwe,全局搜索哪里调用了extAllocIfNeeded()方法,在attachCategories()、class_setVersion()、demangledName()、addMethods_finish()、class_addProtocol()、class_addProperty()、_class_addProperty()中会调用extAllocIfNeeded()方法初始化rwe。今天的核心就分类的处理,所以把核心先放在attachCategories()方法上。
- 那么再全局搜索下attachCategories()是在哪里调用的,在
attachToClass()和load_categories_nolock()方法中调用了attachCategories() - 再全局搜索attachToClass()哪里调用了,发现在
methodizeClass()中调用,而methodizeClass()又是在realizeClassWithoutSwift中调用的,_read_images中调又用了realizeClassWithoutSwift。 - 再反推跟下load_categories_nolock(),在
loadAllCategories()中调用了load_categories_nolock(),load_images()中又调用了loadAllCategories()
总结:根据反推法我们在源码中发现了两个流程调用了attachCategories()
_read_images->realizeClassWithoutSwift->methodizeClass->attachToClass->attachCategories->rweload_images->loadAllCategories->load_categories_nolock->attachCategories->rwe
attachCategories()
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{ //...
//苹果给我们分类中方法的个数设置了一个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];
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
//创建rwe
auto rwe = cls->data()->extAllocIfNeeded();
//遍历分类
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
//获取分类方法
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
//如果方法等于64
if (mcount == ATTACH_BUFSIZ) {
//方法排序
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
//把mlist指针放进ATTACH_BUFSIZ-1的位置,此时mcount=1
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;
}
}
//上面++mcount时mcount=1>0
if (mcount > 0) {
//mlists + ATTACH_BUFSIZ - mcount是内存平移
//mlists内存平移ATTACH_BUFSIZ-1的位置
//排序分类方法列表
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
//rwe 把分类方法添加进主类
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
//...
}
//添加属性进主类
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
//协议进主类
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
分析:根据注释attachCategories的作用是创建rwe,然后获取类的所有分类,遍历所有分类,获取分类排完序的方法添加进主类,以及获取属性、协议添加进主类别,我们看下添加进主类的方法attachLists。
attachLists
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
//只要setArray 此时为真
if (hasArray()) {
//many lists -> many lists
//获取主类中的方法个数oldCount
uint32_t oldCount = array()->count;
//新的个数等于oldCount+addedCount
uint32_t newCount = oldCount + addedCount;
//重新开辟大小
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;
//遍历oldcout,把主类中的方法放在newArray的后面
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i];
//把添加的方法放在newArray的前面
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i];
//是否旧的
free(array());
//赋值新的
setArray(newArray);
validate();
}
//主类没有list
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else {
//主类有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();
}
}
分析:attachLists的作用就是遍历分类中的方法或者属性或者协议,把分类中添加的放在主类的列表前面,把主类中原来的放在列表的后面,以方法为例,也就是说分类中的方法在主类的前面,所以调用一个同名方法会先调用分类中的,这可能也是一个面试题哦~
我们分析了attachCategories方法的作用,但是什么时候调用的呢?它的调用堆栈是什么样的?上面我们通过反推法分析了源码调用流程如下
- _read_images->realizeClassWithoutSwift->methodizeClass->attachToClass->attachCategories->rwe,这是非懒加载的流程,也就是说必须要实现load()方法,那么主类实现load()还是分类实现呢?
- load_images->loadAllCategories->load_categories_nolock->attachCategories->rwe,要走这个流程,那么必须要有load()方法,因为load_images就是加载类的load()方法。下面就通过几种情况分析一下到底是
load()方法对attachCategories的影响。
主类非懒加载+分类非懒加载
我们新建一个LGPerson的分类,LGPerson主类实现load(),LGPerson的分类也实现load(),在attachCategories处下个断点看一下调用堆栈
我们看到调用堆栈
load_image->loadAllCategories()->load_categoried_nolock()->attachCategories,确实是调用了attachCategories方法把分类中方法添加进了主类中
再来看下macho中分类表和非懒加载表
非懒加载分类表以及分类表值都对应上,没毛病。
主类非懒加载+分类懒加载
LGPerson主类实现load(),LGPerson的分类不实现load(),同样在attachCategories出下断点
根据调用堆栈发现没有调用attachCategorie,也就是说分类没有实现load()就不会调用attachCategorie方法,那么分类中的方法去哪了呢?
再看下macho中分类表
分类表怎么是空的?难道分类没有实现load()方法编译时就会被优化掉吗,是优化进类中了吗?我们在初始化类方法realizeClassWithoutSwift中断点看一下ro
果然分类中的方法已经添加进了主类中。也就是说,如果
分类是懒加载即没有实现load(),在编译时就会把分类中的方法、属性等放进主类中,并且分类被优化掉了,因为分类表中没有该分类。
主类懒加载+分类非懒加载
LGPerson主类不实现load(),LGPerson的分类实现load(),同样在attachCategories出下断点
根据调用堆栈发现也没有调用attachCategorie,也就是说主类没有实现load()就不会调用attachCategorie方法即使分类实现也没用,貌似被
编译器优化了,我们再来看下Macho
macho中类表,非懒加载类表、分类表以及非懒加载分类表
我们的分类明明是非懒加载,但是非懒加载分类表中却没有并且分类中也没有,而主类LGPerson明明没有实现load(),但是非懒加载类表中却存在。说明
编译时会把主类强制优化成非懒加载类同时把分类优化掉,把分类中方法放进主类中。这个跟上面主类非懒加载+分类懒加载是一样的,只是会强制把主类变成非懒加载类。
主类非懒加载+分类非懒加载
LGPerson主类不实现load(),LGPerson的分类不实现load(),同样在attachCategories处下断点,发现断点并没有走,而且也没有走realizeClassWithoutSwift方法,想想也对,没有实现load()那么也就是懒加载,懒加载类是在方法第一次调用是才加载。我们调用一下LGPerson alloc方法看一下。
走的是lookUpImpOrForward慢速方法查找流程,最后调用了realizeClassWithoutSwift初始化类。
总结
分类是非懒加载即没有实现load(),在编译时就会把分类中的方法、属性等放进主类中,并且分类被优化掉了,不会调用attachCategories()主类懒加载没有实现load(),而分类是非懒加载,编译时会把主类强制优化成非懒加载类同时把分类优化掉,把分类中方法放进主类中,不会调用attachCategories()。