前言
上一篇博客我们将解了类的加载,这篇文章我们主要探索一下分类是如何加载。
分类的结构
- 新建
QHPerson+QHA
分类,然后运行clang -rewrite-objc QHPerson+QHA.m -o QHPerson+QHA.cpp
命令,得到QHPerson+QHA.cpp
文件 2.在代码找到了实际上分类也是结构体category_t
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;
};
其中name
:也就是分类的名字,比如QHA
,cls
表示关联的类。还有实例方法
、类方法
、协议
、属性
。
rwe什么时候初始化
从上一篇博客中知道,加载分类会调用attachCategories
方法,但是加载之前必须存在rwe
:
在
attachCategories
中我们同样找到初始化方法cls->data()->extAllocIfNeeded()
。也就是在添加分类前会调用。
延伸
除了添加分类,什么时候也会初始化rwe呢?在源码全局搜索extAllocIfNeeded
,发现动态的添加方法
、协议
,属性
,都会初始化。
#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();
}
}
- 这个算法分为三种情况
- 第一次如果主类的没有方法,那么分类的方法之间加进去
- 如果主类有方法,并且只有一个分类,那没将主类的方法先放到最后,让后在前面插入分类的方法
- 如果分类有多个,那么先将之前的方法放入数组,让后在前面插入新的分类方法。 分类方法和主类方法排列如下图:
不同情况分析分类的加载情况
下面分不同的情况主类和分类是否有load
方法的组合来探索,分类方法是什么时候添加到methods
里面
1.主类有load,分类也有load
在realizeClassWithoutSwift
方法添加断点:
发现
ro
里面并没有分类方法,继续执行后堆栈会发生变化,会通过load_images->loadAllCategories->load_categories_nolock->attachCategories
这样的流程添加分类
2.主类有load,分类没有load
跟踪断点调试:realizeClassWithoutSwift->methodizeClass->attachToClass
并没有调用添加分类的方法
在realizeClassWithoutSwift
查看ro里面方法的数据:
3.主类没有load,分类有load
跟踪断点调试:realizeClassWithoutSwift->methodizeClass->attachToClass
并没有调用添加分类的方法
重复2
的操作,发现和2
一样,也是在编译阶段就已经加入了分类数据
4.主类没有load,分类也没有load
发现直接进入了main函数,所有的断点都没有走
然后继续执行:
发现发送消息的时候会调用起
realizeClassWithoutSwift
方法去实现,打印ro的值,发现也有分类和类的方法。
小结
通过上面的几种情况分析,分类的加载分不同的情况,主类和分类是否实现load方法会影响分类加载时间。总的来说如果我们在分类里面些过多的load方法,会影响dyld的启动程序速度。如无必要我们尽量减少在主类和分类中添加load方法。等到程序启动后懒加载时再去加载。
类扩展
- 类扩展几乎,在我们开发过程中几乎非常常用.每当我没不想暴露属性,或者方法的时候会在类扩展里面声明。
- 类扩展可以直接在
.m
里面添加,也可以单独创建.h
文件 为了方便编译到一起,在.m
里面添加类扩展,通过clang -rewrite-objc QHPerson.m -o QHPerson.cpp
编译
发现扩展方法也编译进去了,也就是说类扩展的方法在编译阶段就和类绑定到了一起。
验证
在主类里面实现load方法,运行objc源码:在realizeClassWithoutSwift
下断点,
打印ro里面的方法
同样在ro里面也有,这一点证实了我们的结论。
分类关联对象
查看源码
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
object
:要关联的对象
key
:标识符
value
:关联value
policy
:关联策略
_object_set_associative_reference
disguised
:将objc对象包装成统一的数据结构association
:ObjcAssociation association{policy, value},将value和policy包装成统一的格式,以便存储
关联对象结构存储图
关联对象的存储实际上是一个二级的hashmap结构,如下图:
源码验证
添加关联属性,然后调试
打印disguised 和association
AssociationsHashMap
看看AssociationsHashMap &associations(manager.get());
实际上associations
是一个全局的静态变量:
associations
数据接口,有一个Buckets指针
关联对象存储
存储流程分析:
- 第一步先判断之前的表中是否有要关联的对象
- 如果没有先创建或替换。
3.调用
try_emplace
方法
- 根据key寻找bucket,如果有直接返回,如果没有找到,则插入到hash表中。
关联对象移除,
关联对象什么时候释放呢?实际上是在调用delloc的时候
通过源码_objc_rootDealloc->rootDealloc->object_dispose->objc_destructInstance
如下图:
如果有关联对象,会通过
_object_remove_assocations
。