+load()方法
+load() 方法在类/分类第一次加载的时候调用, 并且每个类/分类的+load()方法都会调用
- 测试1, 父类在前, 子类在后
- 测试2, 父类在后, 子类在前
根据前2个测试, 我们可以发现: 不管编译顺序如何, 父类的+load()方法都比子类先调用, 而分类的+load()方法调用顺序与编译顺序有关
源码解析
我们通过源码来研究一下
- load_images() 函数
load_images() 函数对于load()方法主要有预加载和调用
2个操作,
void load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories(); // 加载分类
}
// 如果没有 load()方法 直接返回
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
// 预处理load()方法
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
// 调用 load() 方法
call_load_methods();
}
复制代码
-
prepare_load_methods() 函数
- 预加载
void prepare_load_methods(const headerType *mhdr) { size_t count, i; runtimeLock.assertLocked(); classref_t const *classlist = _getObjc2NonlazyClassList(mhdr, &count); for (i = 0; i < count; i++) { // 加载本类的 load() 方法 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); 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()); // 加载分类的load()方法 add_category_to_loadable_list(cat); /** add_class_to_loadable_list 核心代码 // 获取 load()方法 method = _category_getLoadMethod(cat); // 给list的元素赋值 loadable_categories[loadable_categories_used].cat = cat; loadable_categories[loadable_categories_used].method = method; // 计数+1 loadable_categories_used++; */ } } 复制代码
- schedule_class_load() 函数
static void schedule_class_load(Class cls) { if (!cls) return; ASSERT(cls->isRealized()); // _read_images should realize if (cls->data()->flags & RW_LOADED) return; // 递归父类, 将load方法加入list , 所以这也就是为什么会先调用父类 schedule_class_load(cls->getSuperclass()); // 添加到list中 add_class_to_loadable_list(cls); /** add_class_to_loadable_list 核心代码 // 获取 load()方法 method = cls->getLoadMethod(); // 给list的元素赋值 loadable_classes[loadable_classes_used].cls = cls; loadable_classes[loadable_classes_used].method = method; // 计数+1 loadable_classes_used++; */ cls->setInfo(RW_LOADED); } 复制代码
schedule_class_load() 函数中, 会递归调用到父类, 所以父类的+load()方法会先执行
-
调用+load()方法
- 循环调用+load()方法
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; } 复制代码
- call_class_loads()
static void call_class_loads(void) { int i; // Detach current loadable list. struct loadable_class *classes = loadable_classes; int used = loadable_classes_used; loadable_classes = nil; loadable_classes_allocated = 0; loadable_classes_used = 0; // Call all +loads for the detached list. for (i = 0; i < used; i++) { Class cls = classes[i].cls; load_method_t load_method = (load_method_t)classes[i].method; if (!cls) continue; if (PrintLoading) { _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging()); } // 遍历数组, 直接强转后执行load() 函数 (*load_method)(cls, @selector(load)); } // Destroy the detached list. if (classes) free(classes); } 复制代码
- call_category_loads()
static bool call_category_loads(void) { int i, shift; bool new_categories_added = NO; // Detach current loadable list. struct loadable_category *cats = loadable_categories; int used = loadable_categories_used; int allocated = loadable_categories_allocated; loadable_categories = nil; loadable_categories_allocated = 0; loadable_categories_used = 0; // Call all +loads for the detached list. for (i = 0; i < used; i++) { Category cat = cats[i].cat; load_method_t load_method = (load_method_t)cats[i].method; Class cls; if (!cat) continue; cls = _category_getClass(cat); if (cls && cls->isLoadable()) { if (PrintLoading) { _objc_inform("LOAD: +[%s(%s) load]\n", cls->nameForLogging(), _category_getName(cat)); } // 与call_class_loads相同, 强转后直接调用load()函数 (*load_method)(cls, @selector(load)); cats[i].cat = nil; } } // ....其他代码 return new_categories_added; } 复制代码
从源码我们可以看到, load()是通过C函数直接调用. 所以, 类/分类 的所有load()方法都会执行一次
+initialize()方法
+initialize()方法是在这个类第一次收到消息时调用, 也就是如果不使用到这个类, 就一直不会调用. 我们同样做2个测试
- 测试1, 父类在前
- 测试2, 父类在后
通过测试我们可以发现, 不管父类在前还是在后, 都会调用到父类分类的+initialize()方法, 但是具体调用哪个分类的+initialize()方法, 却跟编译顺序有关(调用后编译类的方法). 这与上篇文章中, 我们探究分类中方法调用的结果类似. 下边我们就通过源码看下原因
源码解析
- lookUpImpOrForward()
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
// 如果初始化过 就进行一次标记
if (slowpath(!cls->isInitialized())) {
behavior |= LOOKUP_NOCACHE;
}
runtimeLock.lock();
checkIsKnownClass(cls);
// 进行cls 的加载
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// ... 进行其他操作
return imp;
}
复制代码
- realizeAndInitializeIfNeeded_locked()
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
runtimeLock.assertLocked();
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
// 如果cls 还没有初始化过, 进行初始化
if (slowpath(initialize && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
return cls;
}
复制代码
- initializeAndLeaveLocked()
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
// 进行了一次调用
return initializeAndMaybeRelock(cls, obj, lock, true);
}
复制代码
- initializeAndMaybeRelock()
static Class initializeAndMaybeRelock(Class cls, id inst,
mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
ASSERT(cls->isRealized());
if (cls->isInitialized()) {
if (!leaveLocked) lock.unlock();
return cls;
}
Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
if (nonmeta->isRealized()) {
lock.unlock();
} else {
nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
cls = object_getClass(nonmeta);
}
ASSERT(nonmeta->isRealized());
// 通过构造的 nonmetaclass, 进行初始化
initializeNonMetaClass(nonmeta);
if (leaveLocked) runtimeLock.lock();
return cls;
}
复制代码
- initializeNonMetaClass() 核心代码
void initializeNonMetaClass(Class cls)
{
ASSERT(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// 递归调用 父类的初始化方法
supercls = cls->getSuperclass();
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
// ...其他代码
if (reallyInitialize) {
// ...其他代码
#if __OBJC2__
@try
#endif
{ // 初始化class
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
objc_thread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: +[%s initialize] "
"threw an exception",
objc_thread_self(), cls->nameForLogging());
}
@throw;
}
@finally
#endif
{
// Done initializing.
lockAndFinishInitializing(cls, supercls);
}
return;
}else {
// ...其他代码
}
}
复制代码
- callInitialize()
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
复制代码
- 从第5步中我们可以看到, 在进行初始化之前, 会递归进行父类的初始化, 所以 父类的 +initialize() 方法会比子类先调用
- 从第6步中我们可以看到, 最后进行调用的是 objc_msgSend(), 这就表示最终的流程进入到了 runtime 的消息发送逻辑. 上一篇介绍Category时, 我们已经解释了分类方法会
"覆盖"
本来方法的原因, 这就是调用父类的+initialize() 实际调用的是分类方法的原因
+load(), +initialize()对比
- 调用次数
- 都只会调用一次
- 调用时机
- +load(): 在runtime加载类/分类时调用
- +initialize(): 在类第一次接收到消息时调用(类初始化时)
- 调用原理
- +load(): 使用C函数直接调用
- +initialize(): 由runtime 的 objc_msgSend()进行调用
- 调用顺序
- +load(): 所有的类/分类都会调用, 父类比子类先调用, 先编译的分类先调用
- +initialize(): 父类的方法只会调用一个
(父类或父类的某个分类)
, 父类比子类先调用, 父类的方法会在每个子类初始化时都调用一次(如果子类没有实现)
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END