Category
分类可以做哪些事情
- 申明私有方法
- 分解体积庞大的类文件
- 将framework方法私有化
特点
可以添加的内容
分类的结构体
/**
* 分类结构体
* @by Si
*/
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);
};
分类的加载调用栈
分类加载调用栈.png
分类源码分析
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
// runtimeLock 需要事先加锁
runtimeLock.assertLocked();
/*
当前类是否是元类
*/
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
// 获取cls中为整合的所有分类,也就是我们创建的Category
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);
}
}
// 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.
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
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:将含有mcout个元素的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);
}
/**
* 拼接类的方法列表
* @param addedLists 传递过来的二维数组
* @param addedCount 长度
* @by Si
*/
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;
/**
内存移动
[[新增的第一个元素], ... [新增的第n个元素], [原有的第一个元素] ... [原有的第n个元素]]
*/
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]));
}
}
总结
- 分类添加的方法会 "覆盖" 宿主类的方法
- 同名分类方法谁能生效取决于编译顺序
- 名字相同的分类会引起编译错误
Extension
能做什么
特点
- 编译时决定
- 只以申明的形式决定,大多数存在.m中
- 不可以为系统类添加扩展
tips: 当多个分类方法重名时,最后编译的分类中的方法会被执行
编译顺序图.png
网友评论