OC语言之Category源码实现
2019-06-14 本文已影响0人
Jimmy_L_Wang
Category源码实现
objc4-750.1版本中Category的定义如下:
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);
};
Category的加载流程
在文件obj-os.mm文件中初始化
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
category被附加到类上面是在map_images
的时候发生的,在new-ABI的标准下,_objc_init
里面的调用的map_images
会调用objc-os.mm文件中的map_images_nolock
,然后调用objc-runtime-new.mm
里面的_read_images
方法,而在_read_images
方法的结尾,有以下的代码片段:
...
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
ts.log("IMAGE TIMES: discover categories");
// Category discovery MUST BE LAST to avoid potential races
// when other threads call the new category code before
// this thread finishes its fixups.
这里做的就是:
-
把category的实例方法、协议以及属性添加到类上
-
把category的类方法和协议添加到类的
metaclass
上
最终调用remethodizeClass
进行category的内容和逻辑加载:
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
/*
我们只分析分类当中实例方法添加的逻辑,
因此在这里我们假设`isMeta = NO`
*/
isMeta = cls->isMetaClass(); //判断当前类是否为元类对象
// Re-methodizing: check for more categories
//获取cls中未完成整合的所有分类 cats意为分类的列表
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
//将分类cats拼接到cls上
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
然后调用函数attachCategories
添加方法
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
/*
我们只分析分类当中实例方法添加的逻辑,
因此在这里我们假设`isMeta = NO`
*/
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方法的含义是将含有mcount个元素的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);
}
如果两个分类中都有一个同名的方法,那个方法最终会生效?
答:最后编译的分类会生效。
/*
addedLists: 传递过来的二维数组(假设传过来的数组为3个数组列表,据此分析)
[[method_t, method_t, ...], [method_t], [method_t, method_t, method_t, ...]]
-------------------------- ---------- ------------------------------------
分类A中的方法列表(A) B C
addedCount = 3;
*/
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return
if (hasArray()) {
// many lists -> many lists
//列表中原有元素总数 假设oldCount = 2
uint32_t oldCount = array()->count;
//拼接之后的元素总数
uint32_t newCount = oldCount + addedCount;
//根据新总数重新分配内存
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
//重新设置元素总数
array()->count = newCount;
/*
内存移动
[[], [], [], [原有的第一个元素], [原有的第二个元素]]
*/
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
/*
内存拷贝
[
A -----> [addedLists中的第一个元素]
B -----> [addedLists中的第二个元素]
C -----> [addedLists中的第三个元素]
[原有的第一个元素]
[原有的第二个元素]
]
这也是分类方法会“覆盖“宿主类的方法的原因
*/
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或编译的过程中,会把我们添加的分类名称,以下划线的形式添加到宿主类中)