在底层原理总结 — Category2中我们看到了Category
在编译的时候的结构
思考以下几个问题:
1、Category的原理是啥?
2、当我们调用Category的方法是怎么调用的?
3、当Category中和类中存在相同方法的时候,最终调用的是哪一个?
4、当多个Category中存在相同方法的时候,最终调用的是哪一个?
带着这几个问题我们看下objc的源码,来分析下流程。
下载源码:https://opensource.apple.com/tarballs/objc4/
顺着下面的方法直接找到最终目的地
void _objc_init(void)
map_images
map_images_nolock
_read_images
读取镜像
remethodizeClass
重新组织下class和meta-class中的方法
attachCategories
附加分类
/**
Class cls: 类对象[Person class]
category_list *cats :分类 [Person (Category1),Person (Category2)]
*/
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
// 是否是元类对象
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
/**方法数组
[
[Category2-方法1,Category2-方法2],
[Category1-方法1,Category1-方法2]
]
*/
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
/**属性数组
[
[Category2-属性1,Category2-属性2],
[Category1-属性1,Category1-属性2]
]
*/
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
/**协议数组
[
[Category2-协议1,Category2-协议2],
[Category1-协议1,Category1-协议2]
]
*/
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 数组中
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
// 获取分类的属性列表
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
// 将获取到的属性列表添加到 proplists 数组中
proplists[propcount++] = proplist;
}
// 获取分类的协议列表
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
// 获取类对象中的数据
auto rw = cls->data();
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);
}
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()->lists 的 oldCount * sizeof(array()->lists[0])个字符挪动到 array()->lists + addedCount 位置
// 换句话说,就是把之前的列表的位置移动到最新扩容列表的最后
memmove(array()->lists + addedCount,
array()->lists,
oldCount * sizeof(array()->lists[0]));
// 将所有分类列表,挪动到class的列表中去
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]));
}
}
最终总结流程图
分类的方法合并.png
我们可以通过runtime的方法查看下Person类中的相关方法,可以看到最终的类中的方法是,类+分类的方法的合并。
/// 获取类对象中的方法
/// @param class class
- (void)getAllMethodList:(Class)class {
unsigned int count = 0;
Method *methodList = class_copyMethodList(class, &count);
for (NSInteger i = 0; i < count; i++) {
Method subMethod = methodList[i];
NSLog(@"%@",NSStringFromSelector(method_getName(subMethod)));
}
}
NSLog(@"Person ------ 所有的对象方法");
[self getAllMethodList:[Person class]];
查看Person中所有的对象方法.png
NSLog(@"Person ------ 所有的类方法");
[self getAllMethodList:object_getClass([Person class])];
查看Person中所有的类方法.png
总结
1、Category的原理是啥?
Category编译之后的底层结构是struct category_t
,里面存储着分类的对象方法,类方法,协议,属性信息。
在程序运行的时候,runtime会将Category的数据合并到类中<类对象和元类对象>
2、当我们调用Category的方法是怎么调用的?
参考第一条问题答案。
调用对象方法:通过对象的isa找到类对象,找到类对象中的方法,调用
调用类方法:通过类对象的isa找到元类对象,扎到元类对象中的类方法,调用
3、当Category中和类中存在相同方法的时候,最终调用的是哪一个?
调用的是Category中的方法
因为类方法列表已经放到整体方法列表的后面了。
所以调用相同方法的时候,会优先遍历到分类中的方法
4、当多个Category中存在相同方法的时候,最终调用的是哪一个?
会调用后编译的分类中的方法
因为runtime将分类中的方法列表合并到类对象中的时候,是倒叙遍历的,所以后被编译的分类中的方法列表优先被加到类对象中。
image.png image.png
那么分类的编译顺序在哪里查看呢?
image.png
网友评论