「这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战」。
再来看看attachLists
这个方法是如何将list合二为一的。
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;
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]));
}
}
复制代码
这里主要看memmove 和 memcpy
这俩函数,memmove
将原来的list
往后移了addedCount
,而memcpy
则是将新的addedLists
复制到了原来list
的前面。这就说明了分类覆盖主类的同名方法并不是真正的覆盖,而是分类的方法被排到了前面,当方法查找时,则返回了分类的方法实现
调用被分类插队的同名方法
在分类和主类都添加sayYes
方法,再在分类sayNo
方法里输入以下代码
unsigned int methodCount = 0;
Method *methodList = class_copyMethodList([self class], &methodCount);
IMP lastIMP = nil;
for (NSInteger i = 0; i < methodCount; ++i) {
Method method = methodList[i];
NSString *selName = NSStringFromSelector(method_getName(method));
if ([@"sayYes" isEqualToString:selName]) {
lastIMP = method_getImplementation(method);
((void(*)(void))lastIMP)();
}
}
复制代码
会发现主类和分类的sayYes
都走了
顺便捋一捋+load
方法的顺序
在 call_load_methods
之前,会调用prepare_load_methods
去发现+load
方法,而这里面会对非懒加载类和非懒加载分类做处理。
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
//printf("%s--%s\n",cls->nameForLogging(),cat->name);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
复制代码
进去schedule_class_load
方法,里面有一段代码
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
复制代码
在将类的+load
方法添加进类的加载列表的时候,会schedule_class_load
先将父类的+load
加进去,这样保证父类在子类之前调用+load
,然后添加分类的+load
方法到分类的加载列表,而分类的+load
加载顺序则看文件编译的顺序了。
真正调用+load
方法的是call_load_methods
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
复制代码
从代码里可以看出来,首先循环调用类的load
方法,再调用分类的load
方法。
验证一下
这里可能想到 分类里的+load
为啥没有'覆盖' 类的+load
方法打印呢,这里并没有做什么特别的处理,分类的+load
确实'覆盖' 了类的+load
方法前面了。
但是call_load_methods
的时候,并没有走消息转发的机制(objc_msgSend
),而是直接通过函数指针直接调用的((*load_method)(cls, @selector(load));
),所以只要实现了就会调用。
总结以下load
的调用顺序:父类先于子类调用,主类先于分类调用,分类则看文件编译顺序
分析就到这里,有误请大佬指正。