探讨为非兼容Objective-C的Swift类型提供兼容的最优模式
以下是几种解决Swift属性无法直接暴露给Objective-C的优化模式,可解决手动编写大量兼容代码的繁琐问题,同时提升OC端的使用体验:
方案一:封装OC兼容的包装器类
创建专门供OC调用的包装器类,将所有Swift专属属性转换为OC可识别的类型,通过同步逻辑关联原Model属性。OC端可通过包装器一次性初始化或修改所有属性,避免逐个调用方法。
示例代码:
// 原Swift Model class Model { var b: Bool? var i: Int? var e: MyEnum // 假设MyEnum是Swift专属枚举,如case a, b, c } // OC兼容的包装器类 @objcMembers class ModelOCWrapper: NSObject { var isB: NSNumber? var iValue: NSNumber? var eRawValue: NSString? // 对应MyEnum的String类型rawValue // 同步到原Model func syncToModel(_ model: Model) { model.b = isB?.boolValue model.i = iValue?.intValue if let rawValue = eRawValue as String?, let enumValue = MyEnum(rawValue: rawValue) { model.e = enumValue } } // 从原Model同步初始化 convenience init(model: Model) { self.init() isB = model.b.map(NSNumber.init(value:)) iValue = model.i.map(NSNumber.init(value:)) eRawValue = model.e.rawValue as NSString } } // 给原Model扩展@objc接口 extension Model { @objc var ocWrapper: ModelOCWrapper { get { ModelOCWrapper(model: self) } set { newValue.syncToModel(self) } } @objc convenience init(ocWrapper: ModelOCWrapper) { self.init() ocWrapper.syncToModel(self) } }
OC端使用示例:
ModelOCWrapper *wrapper = [[ModelOCWrapper alloc] init]; wrapper.isB = @YES; wrapper.iValue = @123; wrapper.eRawValue = @"a"; Model *model = [[Model alloc] initWithOcWrapper:wrapper]; // 后续修改直接更新包装器同步 model.ocWrapper = wrapper;
优点:OC端调用体验接近原生,支持批量设置;原Model无需大量扩展方法。
缺点:需维护包装器与原Model的同步逻辑,属性变更时需同步更新包装器。
方案二:提供字典批量设置接口
给Model添加@objc的初始化或更新方法,接收NSDictionary参数,在方法内部解析字典并映射到Swift专属属性。
示例代码:
extension Model { @objc convenience init(dictionary: NSDictionary) { self.init() // 解析可选Bool if let bValue = dictionary["b"] as? NSNumber { b = bValue.boolValue } else if dictionary["b"] == NSNull() { b = nil } // 解析可选Int if let iValue = dictionary["i"] as? NSNumber { i = iValue.intValue } else if dictionary["i"] == NSNull() { i = nil } // 解析Swift枚举 if let eRaw = dictionary["e"] as? String, let enumVal = MyEnum(rawValue: eRaw) { e = enumVal } } @objc func update(with dictionary: NSDictionary) { // 复用初始化的解析逻辑更新属性 if let bValue = dictionary["b"] as? NSNumber { b = bValue.boolValue } else if dictionary["b"] == NSNull() { b = nil } if let iValue = dictionary["i"] as? NSNumber { i = iValue.intValue } else if dictionary["i"] == NSNull() { i = nil } if let eRaw = dictionary["e"] as? String, let enumVal = MyEnum(rawValue: eRaw) { e = enumVal } } }
OC端使用示例:
NSDictionary *params = @{ @"b": @YES, @"i": @456, @"e": @"b" }; Model *model = [[Model alloc] initWithDictionary:params]; // 后续更新属性 [model updateWithDictionary:@{@"b": [NSNull null]}];
优点:OC端可通过字典一次性传递所有参数,代码简洁;无需额外维护包装类。
缺点:字典键名需与Swift属性名严格对应,易出现拼写错误;类型安全依赖运行时检查,编译期无法报错。
方案三:优化枚举的OC兼容性
若Swift枚举可修改,直接改为OC兼容类型;若无法修改,添加转换层实现兼容。
可修改枚举的情况
@objc enum MyEnum: String { case a, b, c } class Model { @objc var e: MyEnum // 现在可直接标记@objc }
不可修改枚举的情况
// 原Swift专属枚举 enum MyEnum { case a, b, c } extension Model { @objc enum MyEnumOC: Int { case a, b, c } @objc var eOC: MyEnumOC { get { switch e { case .a: return .a case .b: return .b case .c: return .c } } set { switch newValue { case .a: e = .a case .b: e = .b case .c: e = .c } } } }
优点:枚举可直接在OC中使用原生枚举类型,类型安全;避免字符串或数字硬编码。
缺点:不可修改的枚举需维护转换逻辑,枚举成员变更时需同步更新转换代码。
方案四:代码生成工具自动生成兼容层
当属性数量较多时,使用Sourcery等代码生成工具,根据原Model的属性自动生成@objc兼容的setter/getter或包装器代码,避免手动编写重复代码。
示例Sourcery模板(简化版):
{% for type in types.all where type.name == "Model" %} extension {{ type.name }} { {% for property in type.properties %} {% if property.type.name == "Bool?" %} @objc func set{{ property.name.capitalized }}(_ value: Bool) { {{ property.name }} = value } @objc func unset{{ property.name.capitalized }}() { {{ property.name }} = nil } @objc var is{{ property.name.capitalized }}: NSNumber? { {{ property.name }}.map(NSNumber.init(value:)) } {% elif property.type.name == "Int?" %} @objc func set{{ property.name.capitalized }}(_ value: Int) { {{ property.name }} = value } @objc func unset{{ property.name.capitalized }}() { {{ property.name }} = nil } @objc var {{ property.name.capitalized }}Value: NSNumber? { {{ property.name }}.map(NSNumber.init(value:)) } {% endif %} {% endfor %} } {% endfor %}
优点:完全自动化,避免重复劳动;属性变更时只需重新生成代码,减少人为错误。
缺点:需引入代码生成工具,增加项目配置复杂度;对自定义类型的支持需编写对应模板逻辑。
内容的提问来源于stack exchange,提问作者timbre timbre

