分类是通过runtime在运行时加入到宿主类上的,具体可参见app的启动流程
分类结构体
struct category_t {
// 所属的类名,而不是Category的名字
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的信息添加到Class,包含method、property、protocol
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
isMeta = cls->isMetaClass();
// 从Category哈希表中查找category_t对象,并将已找到的对象从哈希表中删除
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
从对应的类中获取还没有拼接的所有分类
static category_list *
unattachedCategoriesForClass(Class cls, bool realizing)
{
runtimeLock.assertWriting();
return (category_list *)NXMapRemove(unattachedCategories(), cls);
}
获取到Category的Protocol list、Property list、Method list,然后通过attachLists函数添加到所属的类中
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// 按照Category个数,分配对应的内存空间,二维数组
// 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;
// 循环查找出Protocol list、Property list、Method list
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;
}
}
auto rw = cls->data();//获取宿主类中的rw数据,其中包含宿主类的方法列表信息
// 执行添加操作
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);//主要针对 分类中关于内存管理相关方法情况下的一些特殊处理
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);
}
❗️ 分类的方法、协议和属性是如何添加到宿主类的,实际就是用一个二维数组保存,然后倒序遍历分类的各个列表,也就是最后被编译的方法会首先被执行,然后将宿主类的对应列表的元素向后移动相应的位数,将分类对应列表的元素插入进去,即完成了分类添加到宿主类的步骤
❗️ 分类的方法实际并没有覆盖宿主类的方法,而是优先被执行了
❗️ 当分类中的方法与宿主类的方法同名时,会首先执行分类的方法,但当分类中的同名方法只声明没有实现时,系统会执行宿主类的方法,即实现了宿主类的私有方法访问