Objective - C Category(一)定义及底层实现
2020-04-14 本文已影响0人
爱玩游戏的iOS菜鸟
Categoty要点
- Category的使用场合是什么?
- Category的实现原理
- Category和Class Extension的区别是什么?
- Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
- load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?
- Category能否添加成员变量?如果可以,如何给Category添加成员变量?
下面我们一条一条的来学习和理解Categoty主要的几点
(一)什么是分类(Category)? Category主要使用场合
- 什么是Category?
- Category编译之后是一个struct(category_t),
结构体主要包含分类定义的实例方法以及类方法
- Category主要使用场合
- 声明私有方法
- 分解体积庞大的类文件
- 把FrameWork的私有方法公开化(“覆盖”原方法)
(二)Category的实现原理
在学习Category的实现原理之前,我们需要先知道Category的底层结构
(1)Category的底层结构
- Category的定义在
objc-runtime-new.h
文件中:
//重点属性已注解 其余可暂时忽略
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;//属性列表
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta) {
if (isMeta) return nil; // classProperties;
else return instanceProperties;
}
};
另外也可通过将指定的分类编译生成.cpp
文件,也可以查看。
- 如何在runtime源码中,查看category底层具体的实现原理呢?
源码解读顺序:
(1) objc-os.mm
文件
_objc_init
map_images
-
map_images_nolock
找到_read_images(hList, hCount);
方法
(2) objc-runtime-new.mm文件
_read_images
attachCategories
attachLists
-
realloc
、memmove
、memcpy
其中非常重要的方法attachCategories
:
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
//定义3个数组
//二维数组 [[method_t,method_t],[method_t],[method_t,method_t]]
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
//属性数组
//二维数组 [[property_t,property_t],[property_t],[property_t,property_t]]
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
//协议数组
//二维数组 [[protocol_t,protocol_t],[protocol_t],[protocol_t,protocol_t]]
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);
if (proplist) {
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);
}
接下来调用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;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
//将原方法列表的内存往后挪动新增分类方法列表数量的位置 即将原方法放到数组最后面
//具体分类添加方法的顺序,看编译顺序
//[[category1_method_t,category1_method_t],[category2_method_t],[old_method_t,old_method_t]]
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//
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]));
}
}
因此,通过上面对runtime源码的分析,我们还可以看出:
- 如果分类方法与原方法同名,则分类方法优先调用(并没有“覆盖”,“掩盖”更恰当)
- 不同的分类方法同名,则看编译顺序,最后参与编译的优先级更高
编译顺序可以通过编译日志看出,可以在Build Phase -> Compile Sources 中文件的顺序更改
memcpy函数与memmove函数
上面添加方法时,用到了两个函数:memcpy
、memmove
-
void *memcpy(void *__dst, const void *__src, size_t __n);
说明:memcpy功能和memmove相同,但是memcpy中dest和source中的区域不能重叠,否则会出现未知结果。 -
void *memmove(void *__dst, const void *__src, size_t __len);
说明:memmove用于从source拷贝count个字符到dest,如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中
最后将Category的加载处理流程进行整理总结:
Category的加载处理流程
- 通过Runtime加载某个类的所有Category数据
- 把所有Category的方法、属性、协议数据,合并到一个新的大数组中
- 将合并后的Category数据(方法、属性、协议),插入到类原来数据的前面,并优先调用
Category的实现原理
Category在编译之后的底层结构是struct category_t ,存储着对象方法、类方法、协议、属性信息,在程序启动的runtime阶段,会将Category中的数据,合并到(类对象、元类对象)中
Category和Class Extenstion的区别
- Extenstion在编译时数据就已经包含在类信息中 ;Category在runtime才会将数据合并到类信息中
- Extenstion可直接添加私有成员变量,而Category需要通过关联对象才可以;
- Extenstion不能为系统库添加扩展,而Category可以系统库添加分类
PS:重新走一遍分析流程,并将自己所用的分类进行整理,并思考其底层原理