iOS中OC反射加载Swift与OC类的差异原因及统一实现方案
差异产生原因
- Objective-C runtime 本身没有命名空间设计,所有类的标识都是全局唯一的纯类名,因此
NSClassFromString直接传入类名即可匹配到对应的OC类。 - Swift 默认自带命名空间机制,所有未强制指定Objective-C侧类名的Swift类,在runtime层面的完整类名会自动拼接所属target的模块名作为前缀,你观察到的Bundle ID前缀是因为默认场景下模块名和Bundle ID一致,如果你自定义了模块名,前缀会同步变为自定义的模块名。因此直接传入纯Swift类名无法匹配,需要带上模块前缀才能加载成功。
- 额外说明:如果给Swift类加上
@objc(类名)的修饰符,会强制将该类在runtime层面的类名设为不带前缀的纯类名,此时该Swift类的加载逻辑和OC类完全一致。
通用适配方案
核心逻辑为「先尝试纯类名加载,加载失败自动拼接当前模块名重试」,可以同时兼容OC类和普通Swift类的加载需求,不需要硬写前缀。
模块名获取方式
默认可以从主Bundle的Info.plist中取CFBundleName字段,和默认模块名完全匹配,不需要硬编码。如果你的项目自定义了模块名,替换为对应的值即可。
Objective-C 实现
/// 通用类加载方法,兼容OC类和Swift类 /// @param className 纯类名 - (Class)universalClassFromString:(NSString *)className { // 优先匹配OC类、加了@objc固定类名的Swift类 Class cls = NSClassFromString(className); if (cls) return cls; // 拼接模块前缀匹配普通Swift类 NSString *moduleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]; NSString *prefixedClassName = [NSString stringWithFormat:@"%@.%@", moduleName, className]; return NSClassFromString(prefixedClassName); }
Swift 实现
/// 通用类加载方法,兼容OC类和Swift类 /// - Parameter className: 纯类名 /// - Returns: 匹配到的类 func universalClass(from className: String) -> AnyClass? { // 优先匹配OC类、加了@objc固定类名的Swift类 if let cls = NSClassFromString(className) { return cls } // 拼接模块前缀匹配普通Swift类 guard let moduleName = Bundle.main.infoDictionary?["CFBundleName"] as? String else { return nil } let prefixedClassName = "\(moduleName).\(className)" return NSClassFromString(prefixedClassName) }
注意事项
- 上述方法仅适用于加载当前模块下的类,如果是跨模块加载,需要将模块名替换为目标类所属模块的名称。
NSClassFromString本身仅支持加载继承自NSObject的类,如果要加载Swift的结构体、枚举等非NSObject子类类型,需要使用Swift原生的反射机制实现。
内容的提问来源于stack exchange,提问作者user2679041
相关产品推荐
相关产品推荐

