iOS进阶笔记(七)7.1 +load和initialize的加载过程分析
+load方法(不会触发消息机制)
相关runtime源码执行顺序
_objc_init
-> load_images
-> call_load_methods
-> call_class_loads
-> call_category_loads
调用时机:在runtime初始化动态链接器通知的注册,加载镜像时调用(先加载类,后加载分类)
// runtime初始化入口函数(objc-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();
runtime_init();// 初始化runtime
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
// 动态链接器通知的注册
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
// objc-runtime-new.mm
void load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
// 方法预加载
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
// 调用+ load方法
call_load_methods();
}
调用顺序
1、先调用类的+load方法,再调用分类的+load方法
在程序运行时,类和分类的+load方法只加载一次
// objc-loadmethod.mm
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;
// 引入autoreleasePool管理内存: do-while循环执行完毕时,会及时清理中间过程产生的临时变量以及内存资源
void *pool = objc_autoreleasePoolPush();
do {
// 先调用类的+load方法
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads(); // 调用类的+load方法,该函数执行完毕后将loadable_classes_used置0,确保类的+load方法只被调用1次
}
// 再调用分类的+load方法
// 2. Call category +loads ONCE
more_categories = call_category_loads();// 该函执行完后,more_categories = false,确保分类的+load方法只调用1次
// 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方法加载过程:
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;
// 先加载父类方法,再加载子类方法
// 获取该类方法的IMP 即load_method
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
// 通过IMP直接调用load函数(load_method为函数指针)
(*load_method)(cls, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
先调用父类,再调用子类的原因:
1)从load_images
->prepare_load_methods
-> schedule_class_load
可知,通过递归一直向上查找父类
2)父类在前,子类在后,依次将类和IMP存入loadable_class类型的结构体数组中。
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方法,再调用子类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());
add_category_to_loadable_list(cat);
}
}
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
// 确保只被调用一次
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
// 递归一直往上找父类,确保父类先调用
schedule_class_load(cls->getSuperclass());
// 把类加载到数组中,父类在前,子类在后
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) {
// 动态扩容:若数组loadable_classes已满,使用realloc实现动态扩容
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
// 将类和IMP存入loadable_class类型的结构体数组中
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
调用类的+load方法小结:
- 多个类先编译,先调用
- 先调用父类的+load方法,再调用子类的+load方法
- 直接获取类的方法的IMP,然后用load_method_t类型的函数指针直接调用类的load方法
2、调用分类的+load方法(多个分类按照编译的先后顺序调用)
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.
// 遍历所有分类,通过IMP指针调用并执行load分类方法,最后再清空cats数组
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
// 获取分类方法的IMP
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {// 若分类存在宿主类,且该类已为+load注册了
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
// 直接获取分类的方法的IMP,然后用load_method_t类型的函数指针直接调用分类load方法
(*load_method)(cls, @selector(load));
// cats数组遍历执行完调用load方法后,删除已加载的分类,清空cats数组
cats[i].cat = nil;
}
}
// Compact detached list (order-preserving)
// 再次检查cats数组是否存在分类,并将存在分类的移到cats数组前面,最终得到只有分类的紧凑数组,节省空间。
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[i];
} else {
shift++;
}
}
used -= shift;
// Copy any new +load candidates from the new list to the detached list.
new_categories_added = (loadable_categories_used > 0);
for (i = 0; i < loadable_categories_used; i++) {
// 动态扩容:当分配的分类数组cats被占用完了,使用realloc实现动态扩容
if (used == allocated) {
allocated = allocated*2 + 16;
cats = (struct loadable_category *)
realloc(cats, allocated *
sizeof(struct loadable_category));
}
// 将原有的分类加载到新分配的动态数组中
cats[used++] = loadable_categories[i];
}
// Destroy the new list.
// 清空原有的数组loadable_categories
if (loadable_categories) free(loadable_categories);
// Reattach the (now augmented) detached list.
// But if there's nothing left to load, destroy the list.
if (used) {
// 若存在新分类,则将当前cats分类数组及相关标志位更新
loadable_categories = cats;
loadable_categories_used = used;
loadable_categories_allocated = allocated;
} else {
// 若不存在新分类加入,则释放cats数组,再将相关变量置为初始状态
if (cats) free(cats);
loadable_categories = nil;
loadable_categories_used = 0;
loadable_categories_allocated = 0;
}
if (PrintLoading) {
if (loadable_categories_used != 0) {
_objc_inform("LOAD: %d categories still waiting for +load\n",
loadable_categories_used);
}
}
return new_categories_added;
}
分类的+load方法小结:
-
将全局分类数组
loadable_categories
赋值给了一个局部分类数组cats
; -
遍历所有分类,通过IMP指针调用并执行load分类方法,最后再清空cats数组;
-
再次检查cats数组是否存在分类,并将存在分类的移到cats数组前面,最终得到只有分类的紧凑数组,节省空间;
-
若存在新的分类添加,则将其拷贝到紧凑数组中( 动态扩容:当分配的分类数组cats被占用完了,使用realloc实现动态扩容);
-
清空原有的数组loadable_categories
-
若存在新分类,则将当前cats分类数组及相关标志位更新;
-
若不存在新分类加入,则释放cats数组,再将相关变量置为初始状态
类和分类的+load方法和自定义方法加载区别
加载自定义方法(setter方法等)通过isa基于objc_mgsSend()消息转发方式处理
-
优先调用分类方法
-
再调用类的方法
加载+load的方法直接找到类和分类的IMP,采用函数指针直接调用load方法
- 优先调用类的+load方法
- 再调用分类的+load方法
+initialize方法(会触发消息机制)
执行顺序为:lookUpImpOrForward
-> realizeAndInitializeIfNeeded_locked
-> initializeAndLeaveLocked
-> initializeAndMaybeRelock
-> initializeNonMetaClass
-> callInitialize
-> objc_msgSend(cls, @selector(initialize))
具体源码:
// 启动方法查找
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
// 调用汇编 STATIC_ENTRY __objc_msgForward_impcache
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 = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
curClass = cls;
// 方法查找机制
for (unsigned attempts = unreasonableClassCount();;) {
// 一、当前类中查找
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
// 1、先去当前类对象cache列表中查找是否有该方法
// cache_getImp实现在 objc-msg-arm64.s 中STATIC_ENTRY _cache_getImp
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
// 若没找到,则去父类中查找
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// 2、缓存中没有,再去当前类对象的方法列表(class_rw_t)中找
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
// 获取方法实现,goto到done,将该方法条件到当前类的cache中
imp = meth->imp(false);
goto done;
}
// 若没找到方法实现且动态方法解析失败,则执行消息转发
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
//
imp = forward_imp;
break;
}
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// 二、父类缓存查找
// Superclass cache.
imp = cache_getImp(curClass, sel);
// 逐级向上层父类查找,若找到则停止查找,直接调用该方法,且不缓存
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// 若消息发送失败,则启动动态方法解析
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
// 将查找到方法添加到cache
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
runtimeLock.unlock();
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
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
}
// 若没有被初始化过,则执行下面函数
if (slowpath(initialize && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// If sel == initialize, class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
return cls;
}
// Locking: caller must hold runtimeLock; this may drop and re-acquire it
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
return initializeAndMaybeRelock(cls, obj, lock, true);
}
static Class initializeAndMaybeRelock(Class cls, id inst,
mutex_t& lock, bool leaveLocked)
{
......
initializeNonMetaClass(nonmeta);
......
}
void initializeNonMetaClass(Class cls)
{
......
// 执行初始化程序,即objc_msgSend()
callInitialize(cls);
......
}
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
+initialize方法小结:
- 单纯引入一个类,并不会触发
+initialize
方法执行;只有当该类进行初始化(第一次消息发送消息objc_msgSend()),才会去执行。
调用顺序
-
先调用父类的+initialize,再调用子类的+initialize (先初始化父类,再初始化子类,每个类只会初始化1次)
-
如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
-
若分类实现了+initialize,则会覆盖类本身的+initialize调用
以上(完)
(限于水平,本文可能存在瑕疵甚至错误的地方。如有发现,还请留言指正,相互学习。thx! )
KEEP LOOKING, DON`T SETTLE!