在 Objective-C 开发中,分类(Category)是扩展类能力的核心手段,而在分类中实现 +load 方法,是实现无侵入全局能力的经典方案,全程仅需掌握核心规则和少量代码即可落地。
一、核心基础规则
和普通分类方法会覆盖主类同名方法不同,+load 是 Runtime 特殊处理的方法:分类中的 +load 永远不会覆盖主类的 +load,主类和所有分类的 +load 都会被系统完整调用,不存在遗漏。
二、加载执行全流程
整个过程完全由系统在 App 启动阶段自动触发,执行顺序100%确定:
-
第一阶段:主类方法执行
严格遵循「父类优先于子类」的顺序执行所有主类的+load,比如先执行NSObject +load,再执行它的子类,子类的子类,不会出现子类先于父类执行的情况。 -
第二阶段:分类方法执行
必须等待对应主类的+load完全执行完毕后,才会依次执行该类所有分类的+load,多个分类的执行顺序和 Xcode 项目里的文件编译顺序保持一致。
举个直观例子:定义父类 Person、子类 Student,给两个类各加一个分类,最终执行顺序固定为:
Person +load → Student +load → Person(Category) +load → Student(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条必守避坑准则
- 禁止在
+load中写耗时逻辑,它在主线程同步执行,会直接拉长 App 启动耗时,严重时可能触发苹果审核拒件。 - 不要在方法内调用其他自定义类的业务方法,
+load执行时大量类还未完成加载,极易触发「unrecognized selector」崩溃。 - 不要依赖多个分类的
+load执行顺序,避免在不同分类的+load之间做逻辑依赖,防止后续调整文件编译顺序后逻辑异常。
最佳实践:+load 方法应当保持独立且自包含,只处理自身类或全局静态资源的初始化,不要与其他类的加载逻辑产生耦合。