+load方法是ObjC运行时在类被加载时自动调用的方法,所有+load方法在main函数之前同步执行,会阻塞启动。


问题分析

+load方法的特点:

特性说明
调用时机main函数之前,ObjC Runtime初始化时
调用顺序父类 → 子类 → Category
线程主线程,同步执行
影响直接阻塞启动
// 问题代码:+load中执行耗时操作
@implementation HeavyModule
+ (void)load {
    // 耗时操作会阻塞启动
    [self setupDatabase];
    [self preloadResources];
    [self registerServices];
}
@end

+load与+initialize的区别

特性+load+initialize
调用时机main之前类首次使用时
调用次数只调用一次可能多次(子类触发)
是否阻塞启动
线程安全
调用顺序父类→子类→Category父类→子类

更详细的对比请参考:+load与+initialize的区别


优化方案

方案1:使用+initialize替代

+load中的逻辑迁移到+initialize,延迟到类首次使用时执行:

@implementation HeavyModule

// 优化前
+ (void)load {
    [self setupDatabase];
}

// 优化后
+ (void)initialize {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 延迟到类首次使用时执行
        [self setupDatabase];
    });
}

@end

注意+initialize可能被子类触发多次调用,需要使用dispatch_once保证只执行一次。

方案2:延迟到启动完成后执行

只在+load中做轻量级注册,将实际工作延迟到启动完成后:

@implementation HeavyModule

+ (void)load {
    // 只做轻量级注册
    [[LaunchTaskManager shared] registerTask:^{
        [self setupDatabase];
        [self preloadResources];
    }];
}

@end

启动任务管理器的实现:

class LaunchTaskManager {
    static let shared = LaunchTaskManager()
    
    private var tasks: [() -> Void] = []
    
    func registerTask(_ task: @escaping () -> Void) {
        tasks.append(task)
    }
    
    func executeTasks() {
        // 在首帧渲染后执行
        DispatchQueue.main.async {
            for task in self.tasks {
                task()
            }
            self.tasks.removeAll()
        }
    }
}

方案3:使用静态注册替代动态注册

很多+load的使用场景是为了注册某些服务或路由,可以改用静态注册:

// 优化前:动态注册
@implementation ModuleA
+ (void)load {
    [[Router shared] registerPath:@"/moduleA" handler:self];
}
@end

// 优化后:静态注册(在plist或代码中配置)
// 在AppDelegate中统一注册
- (void)setupRoutes {
    NSDictionary *routes = @{
        @"/moduleA": @"ModuleA",
        @"/moduleB": @"ModuleB",
    };
    [[Router shared] registerRoutes:routes];
}

方案4:使用Swift替代ObjC

Swift没有+load方法,可以使用懒加载或其他方式实现相同功能:

class HeavyModule {
    // 使用懒加载,首次访问时才初始化
    static let shared: HeavyModule = {
        let instance = HeavyModule()
        instance.setupDatabase()
        return instance
    }()
    
    private func setupDatabase() {
        // 数据库初始化
    }
}

如何找出所有+load方法

方法1:使用Instruments

在Instruments的Time Profiler中可以看到所有+load方法的调用和耗时。

方法2:使用脚本扫描

# 在项目目录下搜索所有+load方法
grep -r "+ (void)load" --include="*.m" .