说明:本文涉及到runtime源码(objc4-756.2)中的objc-runtime-new.h/objc-runtime-new.mm文件.
首先来看category数据结构:
//category数据结构
struct category_t {
const char *name;//分类名称
classref_t cls;//分类所属的宿主类
struct method_list_t *instanceMethods;//实例方法列表
struct method_list_t *classMethods;//类方法列表
struct protocol_list_t *protocols;//协议方法列表
struct property_list_t *instanceProperties;//属性列表
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
从category_t数据结构中不难看出,我们可以为分类当中添加实例方法、类方法、协议以及属性,在这里只以添加实例方法为例.
分类加载调用栈:
_objc_init(runtime初始化) —> map_2_images(镜像相关处理 )—>map_images _nolock(镜像相关处理)—> _read_images(读取镜像)—> remethodizeClass
上面是对加载分类的一个简单流程说明,分类的实现逻辑都在remethodizeClass中,因此我们最主要的是看remethodizeClass实现.
/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list.
* Updates method caches for cls and its subclasses.
* Locking: runtimeLock must be held by the caller
将未完成整合的分类附加到现有的类中
整理类的方法列表,协议列表,属性列表
更新类及其子类的方法缓存
Locking:runtimeLock必须由调用者持有
**********************************************************************/
static void remethodizeClass(Class cls)
{
category_list *cats;//分类列表
bool isMeta;//是否为元类
runtimeLock.assertLocked();
/*
分析分类当中实例方法添加的逻辑
所以假设isMeta = NO
*/
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
//unattachedCategoriesForClass函数获取cls中未完成整合的所有分类
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
//将分类cats拼接到cls上
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
attachCategories函数内部实现:
#pragma mark - 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.
//附加分类的方法、协议、属性到类中
//假设cats中的类别都已加载并按加载顺序排序,
static void attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
/*
分析分类当中实例方法添加的逻辑
so,假设isMeta = NO
*/
bool isMeta = cls->isMetaClass();
/*
声明3个局部变量 分别是方法列表、属性列表、协议列表 都是二维数组
如method_list_t二维数组结构
[[method_t,method_t,...],[method_t],[method_t,method_t,method_t],...]
*/
// fixme rearrange to remove these intermediate allocations
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;//宿主类分类的总数
bool fromBundle = NO;
while (i--) {//这里是倒叙遍历,最先访问最后编译的分类
//获取一个分类
auto& entry = cats->list[i];
//获取该分类的方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
//最后编译的分类最先添加到分类数组中
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
//属性列表添加规则 同方法列表添加规则
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
//协议列表添加规则 同方法列表添加规则
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
//获取宿主类当中的rw数据,其中包含宿主类的方法列表信息
auto rw = cls->data();
//主要是针对 分类中有关于内存管理相关方法情况下,一些特殊处理
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
/*
rw代表类
methods代表类的方法列表
attachLists 方法含义是 将含有mcount个元素的mlists拼接到rw的methods上
*/
//到这里分类的方法才真正的添加到了宿主类中 因此说分类是运行时决议
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
/*
假如有3个分类,分类A中有两个方法,分类B中有一个方法,分类C中有3个方法
那么最终mlists就得到下面的结果
[[method_t,method_t], [method_t], [method_t,method_t,method_t]]
------------------- ---------- -----------------------------
分类A中的方法列表 B C
*/
从上面的内部实现可以看出:
1.Category是运行时决议,它在运行时才把分类方法添加到对应宿主类的方法列表中
2.假如在分类有多个的情况下,如果每个分类都有一个同名的分类方法,那么最终最后编译的分类方法会生效(前面的会被”覆盖“)
因为最后编译的分类方法会位于数组列表的前面位置,在消息发送或者消息函数查找的过程中,会根据选择器的名字来查找,一旦查找到了方法实现就是返回
附加列表函数具体实现:
//附加列表函数具体实现
/*
addedLists传递过来的二维数组
[[method_t,method_t], [method_t], [method_t,method_t,method_t]]
-------------------- ---------- -----------------------------
分类A中的方法列表(A) B C
addedCount = 3
*/
//addedLists:要附加的分类列表 addedCount:二维数组元素个数
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;//判空
if (hasArray()) {
// many lists -> many lists
//列表中原有元素总数 假设oldCount = 2
uint32_t oldCount = array()->count;
//拼接之后的元素总数 5
uint32_t newCount = oldCount + addedCount;
//根据新总数重新分配内存
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
//重新设置元素总数 5
array()->count = newCount;
/*
内存移动
[[],[],[],[原有的第一个元素],[原有的第二个元素]]
*/
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
/*
内存拷贝
[
A ---> [addedLists中的第一个元素],
B ---> [addedLists中的第二个元素],
C ---> [addedLists中的第三个元素],
[原有的第一个元素],
[原有的第二个元素]
]
*/
//memcpy函数把addedLists中的元素拷贝到lists当中
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}//后两个分支是 关于列表当中存储的分类具体采取的是list还是array_t结构的区别,不影响分析分类实现逻辑
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
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;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
从上面的内部实现可以看出:
方法同名情况下,分类方法会”覆盖“宿主类的方法的原因:
宿主类方法仍然存在,但是分类方法位于方法列表数组前面的位置了,在消息函数查找的过程中,根据方法的名字来查找,一旦查找到方法实现就会返回,也即分类方法会被优先实现.