这个系列的文章仅仅对 Category 的实如今源码层面作必定的分析,你们仍是要多上手尝试,你会发现不少有意思的问题,例如:
若是你的工程和你使用的第三方库,产生了方法重名,在你开启了 LTO 优化的先后,你会发现分别调用到了第三方库和你的主工程方法中,这又是为何呢?容许我在开头就挖个坑,这是个颇有意思的问题,等我有空会写一篇文章。c++
这里有几点要说明的:数组
Category相信你们都有使用过,利用分类能够对一些系统类进行扩展,分模块等等。咱们先来构造个分类看一下:markdown
// Person类
- (void)run {
NSLog(@"run");
}
复制代码
// Person+Test 分类
- (void)test {
NSLog(@"test");
}
+ (void)test2 {
NSLog(@"test2");
}
复制代码
这样咱们就能够在引入头文件后调用对应的方法。函数
咱们都知道,调用方法的时候是经过objc_msgSend(self, SEL)来实现的,那么会产生问题:工具
person的类对象中有没有存储分类中的实例方法?Person+Test会不会有本身的分类对象,将分类中的实例方法存放在分类对象中呢?
先说结论:不论有没有分类,每一个类都只有一个类对象,分类中的方法也是存储在Person类的类对象中的。优化
首先咱们 cd 到 Person+Eat 的目录下,而后执行 clang rewrite-objc Person+Test.m,这样Person+Test.m就被转化为了c++的代码 Person+Test.cppui
这个文件比较长,咱们直接看 _category_t 这个结构体,这个结构体是每个分类的结构spa
而后再找到这个结构体初始化的地方:调试
经过这里咱们就能看出,Person+Test的初始化时,类名叫作 Person,有实例方法和类方法,其余都是空,那么让咱们来看看实例方法和类方法初始化的地方:code
不难看出也正好对应这 Person+Test 分类中的两个方法,- (void)test 和 + (void)test2
实际上这些方法、属性、协议,都是在运行时经过runtime进行合并的,要了解这个合并的过程,就须要阅读runtime的源码。下面是本次阅读runtime源码的一些过程:
这个方法中,将全部的category中的方法、属性、协议,整合到二维数组method_list_t mlists中,而后调用 attachLists 方法进行attach,咱们来看一下这个方法里的代码
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;
// 从新分配内存,由于要进行合并,因此原来分配的内存不够了
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
// 将array()原有的list拷贝到新list的尾部
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
// 将新的方法list拷贝到list的头部
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
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]));
}
}
复制代码
这里咱们看到,最终合并的方法列表的存储结构是顺序表,而查找时也是顺序查找的。
分类方法会覆盖原类方法的缘由是由于原类方法被放在了list最后(memmove操做)。
后加载的分类方法会覆盖前面的分类方法是由于,在 map_images_nolock 函数中,采用的是while(i--)的方式,所以后加载的分类会排在前面。
这篇文章中没有把全部的源码拿出来,你们应当对着源码亲自上手看一看。 文中若有错误,欢迎指出。