# iOS 中的 +load 和 +initialize 在继承与分类中的不同表现

**URL:** <https://www.shiqidu.com/t/topic/873>\
**Category:** 开发调优\
**Tags:** C-C-Obj-C\
**Created:** [2025 年11 月 8 日 16:29 UTC](https://www.shiqidu.com/t/topic/873 "2025-11-08T16:29:17Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

作者： ![shiqidu\_editor](https://www.shiqidu.com/user_avatar/www.shiqidu.com/shiqidu_editor/32/528_2.png) [@shiqidu\_editor](https://www.shiqidu.com/u/shiqidu_editor)\
发布日期： [2025 年11 月 8 日 16:29 UTC](https://www.shiqidu.com/t/topic/873/1 "2025-11-08T16:29:17Z")

</div>

## 前言

在 iOS 开发中，+load 和 +initialize 是两个特殊的类方法，它们都是由系统自动调用，且正常情况下每个类只会执行一次。虽然这两个方法看起来相似，但它们在调用时机、调用顺序以及继承和分类中的表现都有显著差异。+load 方法在程序启动时、类被加载到内存时就会调用，而 +initialize 方法则是在类第一次接收到消息时才会调用。本文将深入 [Runtime](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fapple-oss-distributions%2Fobjc4%2Ftree%2Fobjc4-906 "https://github.com/apple-oss-distributions/objc4/tree/objc4-906") 源码，详细分析这两个方法的底层实现机制，并重点探讨它们在继承关系和分类实现中的不同表现，帮助开发者更好地理解和使用它们。

## +load 方法的实现机制

在 [Runtime](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fapple-oss-distributions%2Fobjc4%2Ftree%2Fobjc4-906 "https://github.com/apple-oss-distributions/objc4/tree/objc4-906") 源码中我们可以找到 +load 方法的具体实现细节，整理后的代码如下所示：

> 🔧 我在 [这里](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2FinternetWei%2FObjCRuntimes "https://github.com/internetWei/ObjCRuntimes") 维护了一个可以直接运行调试的 Runtime 项目，欢迎大家下载调试源码。

```cpp
void
load_images(const struct _dyld_objc_notify_mapped_info* info) {
    {
        mutex_locker_t lock2(runtimeLock);
        loadAllCategoriesIfNeeded();
        // 将类和分类中的 load 方法添加到全局数组中，等待后续调用。
        prepare_load_methods((const headerType *)info->mh, info->sectionLocationMetadata);
    }
    // 调用所有的 load 方法。
    call_load_methods();
}

```

从上面的源码分析可以看出，+load 方法的实现过程主要分为两个步骤：

1. 调用 `prepare_load_methods` 函数，将类和分类所有的 +load 方法准备好（其实就是将类和分类中的 +load 方法添加到一个全局数组中）。
2. 调用 `call_load_methods` 函数，遍历前面准备好的数组，并进行调用。

接下来，让我们深入分析 `prepare_load_methods` 函数的具体实现，看看它是如何收集这些 +load 方法的：

```cpp
void 
prepare_load_methods(const headerType *mhdr, const _dyld_section_location_info_t info) {
    size_t count, i;
    classref_t const *classlist = getSectionData<classref_t>(mhdr, 
    info, 
    _dyld_section_location_data_non_lazy_class_list, 
    &count);
    // 按照编译顺序遍历所有类并添加 load 方法。
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }
    category_t * const *categorylist = getSectionData<category_t *>(mhdr, 
    info,
    _dyld_section_location_data_non_lazy_category_list, 
    &count);
    // 按照编译顺序遍历所有分类并添加 load 方法。
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;
        realizeClassWithoutSwift(cls, nil);
        add_category_to_loadable_list(cat);
    }
}
// 准备类的 load 方法。
static void schedule_class_load(Class cls) {
    if (!cls) return;
    // 检查该类的 load 方法是否已经添加过，防止重复添加。
    if (cls->data()->flags & RW_LOADED) return;
    /*
     递归调用，确保父类的 load 方法先添加，
     从而保证先调用父类的 load 方法再调用自身的 load 方法。
     */
    schedule_class_load(cls->getSuperclass());
    // 将当前类的 load 方法添加到全局数组中。
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED);
}
struct loadable_class {
    Class cls;
    IMP method;
};
// 全局数组，用于存储所有需要调用的类的 load 方法。
static struct loadable_class *loadable_classes = nil;
// 将类的 load 方法添加到全局数组中。
void add_class_to_loadable_list(Class cls) {
    IMP method;
    method = cls->getLoadMethod();
    if (!method) return;
    // 第一次进来时先初始化全局数组，以确保它能容纳所有的 load 方法。
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)realloc(loadable_classes,
        loadable_classes_allocated * sizeof(struct loadable_class));
    }
    loadable_classes[loadable_classes_used].cls = cls;
    // 将 load 方法添加到全局数组中。
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}
struct loadable_category {
    Category cat;
    IMP method;
};
// 全局数组，用于存储所有需要调用的分类的 load 方法。
static struct loadable_category *loadable_categories = nil;
// 将分类的 load 方法添加到全局数组中。
void add_category_to_loadable_list(Category cat) {
    IMP method;
    method = _category_getLoadMethod(cat);
    if (!method) return;
    // 第一次进来时先初始化全局数组，以确保它能容纳所有的 load 方法。
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)realloc(loadable_categories,
        loadable_categories_allocated * sizeof(struct loadable_category));
    }
    loadable_categories[loadable_categories_used].cat = cat;
    // 将分类的 load 方法添加到全局数组中。
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

```

通过分析源码，我们可以总结出 +load 方法的加载顺序规则：

1. 系统会按照编译顺序遍历所有的类和分类；
2. 对于类的 +load 方法，系统通过 `schedule_class_load` 函数递归处理，确保了父类的 +load 方法先于子类调用，这保证了继承链上的 +load 方法调用顺序是从父类到子类；
3. 对于分类的 +load 方法，系统通过 `add_category_to_loadable_list` 函数按照编译顺序添加。

接下来，让我们继续分析 `call_load_methods` 函数的具体实现，看看系统是如何调用这些 +load 方法的：

```cpp
void call_load_methods(void) {
    static bool loading = NO;
    bool more_categories;
    // 防止重复调用。
    if (loading) return;
    loading = YES;
    void *pool = objc_autoreleasePoolPush();
    do {
        // 先调用类的 +load 方法。
        while (loadable_classes_used > 0) {
            call_class_loads();
        }
        // 再调用分类的 +load 方法。
        more_categories = call_category_loads();
      // 如果类和分类中还有没调用的 +load 方法，则继续调用。
    } while (loadable_classes_used > 0 || more_categories);
    objc_autoreleasePoolPop(pool);
    loading = NO;
}
// 调用类所有的 +load 方法。
static void call_class_loads(void) {
    int i;
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    // 遍历全局数组并调用所有需要调用的 +load 方法。
    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; 
        // 拿到 load 方法的函数指针进行调用。
        (*load_method)(cls, @selector(load));
    }
    if (classes) free(classes);
}
// 调用分类所有的 +load 方法。
static bool call_category_loads(void) {
    int i, shift;
    bool new_categories_added = NO;
    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;
    // 遍历全局数组并调用所有需要调用的 +load 方法。
    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);
        // 确保类的 +load 方法已经调用了再调用分类的 +load 方法。
        if (cls && cls->isLoadable()) {
            (*load_method)(cls, @selector(load));
            cats[i].cat = nil;
        }
    }
    shift = 0;
    // 将分类中还未调用的 load 方法挪到数组前面。
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[i];
        } else {
            shift++;
        }
    }
    used -= shift;
    // 检查是否有通过运行时动态添加的分类。
    new_categories_added = (loadable_categories_used > 0);
    for (i = 0; i < loadable_categories_used; i++) {
        if (used == allocated) {
            allocated = allocated*2 + 16;
            cats = (struct loadable_category *)realloc(cats, 
            allocated * sizeof(struct loadable_category));
        }
        cats[used++] = loadable_categories[i];
    }
    if (loadable_categories) free(loadable_categories);
    // 如果有通过运行时新添加的分类，将它们赋值给全局数组，等待后续调用。
    if (used) {
        loadable_categories = cats;
        loadable_categories_used = used;
        loadable_categories_allocated = allocated;
    } else {
        if (cats) free(cats);
        loadable_categories = nil;
        loadable_categories_used = 0;
        loadable_categories_allocated = 0;
    }
    // 如果有动态添加的分类，返回 YES，否则返回 NO。
    return new_categories_added;
}

```

通过分析源码，我们可以总结出 +load 方法的调用顺序规则：

1. 系统会先调用类中的 load 方法，然后再调用分类中的 load 方法；
2. 系统在调用分类的 +load 方法时，会有诸多判断，比如是否已经调用过类的 +load 方法，是否有动态添加的分类等等。

在调试的过程中，我发现了一个奇怪的现象，当一个类没有实现 +load 方法，但是它的分类（有且只有一个）实现了 +load 方法时，调用顺序会发生变化。例如以下代码：

```cpp
@implementation Person: NSObject
@end
@implementation Student: Person
+ (void)load {
    NSLog(@"%s", __func__ );
}
@end
@implementation Person (Category1)
+ (void)load {
    NSLog(@"%s", __func__ );
}
@end
@implementation Student (Category1)
+ (void)load {
    NSLog(@"%s", __func__ );
}
@end

```

在这个场景中，我观察到一个奇怪的调用顺序现象。根据 Runtime 源码的实现，理论上应该是先调用完所有类的 +load 方法，然后再调用分类的 +load 方法。但实际运行结果却显示：

1. 首先调用 Person 分类的 +load 方法
2. 然后调用 Student 类的 +load 方法
3. 最后调用 Student 分类的 +load 方法

更奇怪的是，当 Person 类有多个分类都实现了 +load 方法时，调用顺序又会回归到预期：先调用 Student 类的 +load 方法，然后按照编译顺序依次调用所有分类的 +load 方法。

这种特殊现象可能与 Runtime 在处理单个分类时的优化策略有关。当类只有一个分类时，系统可能采用了不同的处理路径，导致调用顺序发生变化。不过，由于这种差异并不影响程序的正确性，且在多分类场景下表现正常，所以这很可能是 Runtime 的一个实现细节，而非 bug。以上观点仅是我的个人猜测，如果大家有更深入的理解，欢迎在评论区分享你的见解。

## +initialize 方法的实现机制

与 +load 方法不同，+initialize 方法是在类第一次接收到消息时才会被调用。这种延迟调用的机制使得 +initialize 方法更适合用于类的初始化工作，因为它可以确保类在被实际使用前完成必要的设置。在 [Runtime](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fapple-oss-distributions%2Fobjc4%2Ftree%2Fobjc4-906 "https://github.com/apple-oss-distributions/objc4/tree/objc4-906") 源码中，我们可以找到 +initialize 方法的具体实现细节，整理后的代码如下所示：

```cpp
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize) {
    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }
    if (!cls || !cls->ISA()) return nil;
    // 检查是否从未调用过 +initialize 方法。
    if (slowpath(initialize && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }
    return cls;
}
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) {
    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);
    }
    // 调用类的 +initialize 方法。
    initializeNonMetaClass(nonmeta);
    if (leaveLocked) runtimeLock.lock();
    return cls;
}
void
initializeNonMetaClass(Class cls) {
    Class supercls = cls->getSuperclass();
    // 先调用父类的 +initialize 方法，然后再调用自身的 +initialize 方法。
    if (supercls && !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    lockClass(cls);
    if (cls->isInitialized()) {
        unlockClass(cls);
        return;
    }
    // 如果类正在其他线程调用 +initialize 方法。
    if (cls->isInitializing()) {
        if (!MultithreadedForkChild || _thisThreadIsInitializingClass(cls)) {
            unlockClass(cls);// 避免死锁和重复调用。
            return;
        } else {
            lockClass(cls);
            _setThisThreadIsInitializingClass(cls);
            performForkChildInitialize(cls, supercls);
        }
    }
    SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
    {
        mutex_locker_t lock(classInitLock);
        cls->setInitializing();
        localWillInitializeFuncs.initFrom(willInitializeFuncs);
    }
    _setThisThreadIsInitializingClass(cls);
    if (MultithreadedForkChild) {
        performForkChildInitialize(cls, supercls);
        return;
    }
    for (auto callback : localWillInitializeFuncs) {
        callback.f(callback.context, cls);
    }
    @try {
        // 调用类的 +initialize 方法。
        callInitialize(cls);
    } @catch (...) {
        @throw;
    } @finally {
        lockAndFinishInitializing(cls, supercls);
    }
}
void callInitialize(Class cls) {
    // 通过消息机制向 cls 发送 initialize 消息。
    objc_msgSend(cls, @selector(initialize));
}

```

通过分析 Runtime 源码，我们可以总结出 +initialize 方法的调用机制和特点：

1. 调用顺序：系统会先调用父类的 +initialize 方法，再调用子类的 +initialize 方法，这保证了继承链上的初始化顺序是从父类到子类；

2. 调用机制：+initialize 方法是通过 objc\_msgSend 消息机制调用的，这意味着：

3. 调用时机：+initialize 方法是在类第一次接收到消息时才会调用，而不是在类被加载到内存时就调用，这与 +load 方法有明显区别。

## +load 和 +initialize 的异同

+load 和 +initialize 的相同点：

- 它们都是由系统自动调用，且正常情况下每个类只会执行一次；
- 它们在继承关系中的调用顺序是一致的，都是先调用父类方法，再调用子类方法；

+load 和 +initialize 的不同点：

- 调用时机不同：+load 在类被加载到内存时就会调用，而 +initialize 则是在类第一次接收到消息时才会调用。这个差异导致了两个重要影响：

- 调用机制不同：+load 方法是通过函数指针直接调用，而 +initialize 方法是通过消息机制调用。这个差异导致了：

基于以上特点，我们可以得出它们各自的最佳实践场景：

+load 方法适用于：

- 需要在 APP 启动时就必须完成的全局初始化工作；
- 框架的自动初始化，比如注册通知观察者、注册路由等；
- 方法交换等运行时操作。

+initialize 方法适用于：

- 类级别的初始化工作，比如初始化类的静态变量；
- 需要根据运行时条件动态初始化的场景；
- 希望延迟到类首次使用时才执行的初始化操作。

下面是一个使用 +load 方法实现框架自动初始化的示例：

```objc
+ (void)load {
    [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(appDidFinishLaunching) name:UIApplicationDidFinishLaunchingNotification object:nil];
}
+ (void)appDidFinishLaunching {
    [NSNotificationCenter.defaultCenter removeObserver:self name:UIApplicationDidFinishLaunchingNotification object:nil];
    // 在这里执行框架的初始化逻辑。
}

```

> ⚠⚠⚠注意：由于 +load 方法是在程序启动前调用，所以它会降低 APP 的启动速度，因此在使用时需要权衡利弊。

> 本文转自 [https://juejin.cn/post/7503913955002941477](https://juejin.cn/post/7503913955002941477)，如有侵权，请联系删除。
