OC 分类中 +load 和 其他静态方法不一样!

在 Objective-C 开发中,分类(Category)是扩展类能力的核心手段,而在分类中实现 +load 方法,是实现无侵入全局能力的经典方案,全程仅需掌握核心规则和少量代码即可落地。

一、核心基础规则

和普通分类方法会覆盖主类同名方法不同,+load 是 Runtime 特殊处理的方法:分类中的 +load 永远不会覆盖主类的 +load,主类和所有分类的 +load 都会被系统完整调用,不存在遗漏。

二、加载执行全流程

整个过程完全由系统在 App 启动阶段自动触发,执行顺序100%确定:

  1. 第一阶段:主类方法执行
    严格遵循「父类优先于子类」的顺序执行所有主类的 +load,比如先执行 NSObject +load,再执行它的子类,子类的子类,不会出现子类先于父类执行的情况。
  2. 第二阶段:分类方法执行
    必须等待对应主类的 +load 完全执行完毕后,才会依次执行该类所有分类的 +load,多个分类的执行顺序和 Xcode 项目里的文件编译顺序保持一致。

举个直观例子:定义父类 Person、子类 Student,给两个类各加一个分类,最终执行顺序固定为:
Person +loadStudent +loadPerson(Category) +loadStudent(Category) +load

// 注意:对于不同父类的类之间,+load 方法的执行顺序不再遵循固定的层级关系,而是主要取决于编译顺序(Compile Order)。

// 假设定义了两个互无继承关系的类:Teacher 和 Doctor,
// 并分别为它们添加了分类 Teacher (Category) 和 Doctor (Category)。

// 有可能情况一:
Teacher +load
Teacher (Category) +load
Doctor +load
Doctor (Category) +load

// 有可能情况二:
Doctor +load
Doctor (Category) +load
Teacher +load
Teacher (Category) +load

三、落地场景与可运行示例

分类中 +load 最核心的两个落地场景,几乎覆盖了日常开发的所有使用需求:

场景1:无侵入全局方法交换(Method Swizzling)

这是分类 +load 最经典的用法,常用于给系统类、第三方类注入全局逻辑,比如全页面埋点、系统方法容错,代码如下:

#import "UIViewController+PageTrack.h"
#import <objc/runtime.h>

@implementation UIViewController (PageTrack)
+ (void)load {
    // 用dispatch_once保证仅执行一次,彻底避免重复交换的风险
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class currentClass = [self class];
        // 定义要交换的两个方法
        SEL originSelector = @selector(viewWillAppear:);
        SEL customSelector = @selector(track_viewWillAppear:);
        
        // 获取方法结构体
        Method originMethod = class_getInstanceMethod(currentClass, originSelector);
        Method customMethod = class_getInstanceMethod(currentClass, customSelector);
        
        // 交换两个方法的实现
        method_exchangeImplementations(originMethod, customMethod);
    });
}

// 自定义注入的埋点逻辑
- (void)track_viewWillAppear:(BOOL)animated {
    // 调用原系统方法,保证原有逻辑不丢失
    [self track_viewWillAppear:animated];
    // 插入自定义业务逻辑
    NSLog(@"页面曝光:%@", NSStringFromClass(self.class));
}
@end

场景2:全局静态资源预初始化

在分类 +load 中提前初始化全局容器,能保证在任何业务代码调用前,资源已经准备完成,完全避免空指针问题,比如之前提到的临时存储分类:

#import "NSObject+TempStorage.h"
#import <objc/runtime.h>

// 全局静态存储容器
static NSMutableDictionary *globalTempStorage;

@implementation NSObject (TempStorage)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 启动阶段提前初始化,无需业务代码手动调用
        globalTempStorage = [NSMutableDictionary new];
    });
}

// 对外提供存取方法
- (void)setTempValue:(id)value forKey:(NSString *)key {
    NSString *uniqueKey = [NSString stringWithFormat:@"%p_%@", self, key];
    globalTempStorage[uniqueKey] = value;
}

- (id)tempValueForKey:(NSString *)key {
    NSString *uniqueKey = [NSString stringWithFormat:@"%p_%@", self, key];
    return globalTempStorage[uniqueKey];
}
@end

四、3条必守避坑准则

  1. 禁止在 +load 中写耗时逻辑,它在主线程同步执行,会直接拉长 App 启动耗时,严重时可能触发苹果审核拒件。
  2. 不要在方法内调用其他自定义类的业务方法,+load 执行时大量类还未完成加载,极易触发「unrecognized selector」崩溃。
  3. 不要依赖多个分类的 +load 执行顺序,避免在不同分类的 +load 之间做逻辑依赖,防止后续调整文件编译顺序后逻辑异常。

最佳实践:+load 方法应当保持独立且自包含,只处理自身类或全局静态资源的初始化,不要与其他类的加载逻辑产生耦合。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容