来自符合协议的未知类的 Swift init

Posted

技术标签:

【中文标题】来自符合协议的未知类的 Swift init【英文标题】:Swift init from unknown class which conforms to protocol 【发布时间】:2021-12-28 22:56:57 【问题描述】:

我目前正在将一个大型项目从 Objective-C 更新到 Swift,但我不知道如何模仿一些逻辑。基本上,我们有一个带有协议的类,该协议定义了几个函数来将任何类转换为自身的 JSON 表示。

该协议如下所示:

#define kJsonSupport @"_jsonCompatible"
#define kJsonClass @"_jsonClass"

@protocol JsonProtocol <NSObject>

- (NSDictionary*)convertToJSON;
- (id)initWithJSON:(NSDictionary* json);

@end

我已经把它改编成这样的 Swift

let JSON_SUPPORT = "_jsonCompatible"
let JSON_CLASS = "_jsonClass"

protocol JsonProtocol

    func convertToJSON() -> NSDictionary
    init(json: NSDictionary)

ObjC 类中的一个函数为符合协议的 NSDictionary 中的每个对象运行 convertToJSON 函数,另一个执行相反的操作,使用 init 函数创建对象的实例。输出字典还包含两个键,一个表示相关字典支持此协议 (kJsonSupport: BOOL),另一个包含转换对象的类的 NSString 表示 (kJsonClass: NSString)。然后 reverse 函数使用这两者来确定对象从哪个类转换,以从给定字典中初始化一个新实例。

所有类对于函数本身都是匿名的。我们只知道每个类都符合协议,因此我们可以在其上调用自定义的 init 函数。

这是它在 ObjC 中的样子:

Class rootClass = NSClassFromString(obj[kJsonClass]);
if([rootClass conformsToProtocol:@protocol(JsonProtocol)])

    Class<JsonProtocol> jsonableClass = (Class<JsonProtocol>)rootClass;
    [arr addObject:[[((Class)jsonableClass) alloc] initWithJSON:obj]];

但是,我不确定如何在 Swift 中实现这种行为。

这是我最好的尝试。我使用 Swiftify 尝试帮助我到达那里,但编译器也不满意:

let rootClass : AnyClass? = NSClassFromString(obj[JSON_CLASS] as! String)
if let _rootJsonClass = rootClass as? JsonProtocol

    weak var jsonClass = _rootJsonClass as? AnyClass & JsonProtocol
    arr.add(jsonClass.init(json: obj))

我在weak var 行和arr.add 行都遇到了几个错误,例如:

非协议、非类类型“AnyClass”(又名“AnyObject.Type”)不能在协议约束类型中使用

'init' 是类型的成员;使用 'type(of: ...)' 来初始化相同动态类型的新对象

参数类型“NSDictionary”不符合预期类型“JsonProtocol”

调用中的无关参数标签“json:”

我有什么方法可以使用自定义协议初始化函数从符合协议的未知类实例化?

【问题讨论】:

谈谈将许多类从 Objective C 转换为 Swift 的经验。可以根据经验说,ObjC 开发人员通常必须发挥创造力来填补 ObjC 标准库中的空白。 swift 上不存在这些差距。因此,通常最好重构代码以适应语言的新功能,而不是直接转换。这甚至可以在保持外部接口不变的情况下完成。所以在这种情况下,不要创建自定义 JSON 协议等,而是尝试重构代码以符合 Codable[String: AnyClass] 字典。 【参考方案1】:

您将来可能会重新考虑此代码,以遵循更多类似 Swift 的模式,但转换并不复杂, 而且我相信您有很多现有代码这依赖于相同的行为方式。

最重要的是所有对象都必须是@objc类。它们不能是结构,它们必须是 NSObject 的子类。这是您希望将其更改为基于 Codable 的更 Swifty 解决方案的主要原因。

您还需要明确命名您的类型。 Swift 将模块名称添加到其类型名称中,这往往会破坏这种动态系统。如果你有一个类型Person,你会想要声明它:

@objc(Person)  // <=== This is the important part
class Person: NSObject 
    required init(json: NSDictionary)  ... 


extension Person: JsonProtocol 
    func convertToJSON() -> NSDictionary  ... 

这确保类的名称是 Person(就像在 ObjC 中一样)而不是 MyGreatApp.Person(在 Swift 中通常是这样的)。

这样,在 Swift 中,这段代码会这样写:

if let className = obj[JSON_CLASS] as? String,
   let jsonClass = NSClassFromString(className) as? JsonProtocol.Type 
    arr.add(jsonClass.init(json: obj))

您缺少的关键部分是as? JsonProtocol.Type。这与+conformsToProtocol: 加上演员表的功能相似。 .Type 表示这是对Person.self 的元类型检查,而不是对Person 的正常类型检查。有关更多信息,请参阅 Swift 语言参考中的 Metatype Type。

请注意,原始的 ObjC 代码有点危险。 -initWithJSON 必须返回一个对象。它不能返回nil,否则此代码将在addObject 调用时崩溃。这意味着实现JsonProtocol 需要对象构造一些东西,即使它传递的 JSON 是无效的。 Swift 会强制执行此操作,但 ObjC 不会,因此您应该仔细考虑如果输入损坏会发生什么。如果您可以使用当前代码使其工作,我很想将 init 更改为可失败或抛出的初始化程序。

我还建议将 NSDictionary 和 NSArray 替换为 Dictionary 和 Array。这应该很简单,无需重新设计代码。

【讨论】:

以上是关于来自符合协议的未知类的 Swift init的主要内容,如果未能解决你的问题,请参考以下文章

Swift 类的 Objective-c 协议。无法识别的方法

符合协议的 Swift 扩展

如何在 Swift 中确认对 Identifiable 协议的枚举?

无法将符合协议的 Objective-C 类分配给具有 Swift 协议的属性

符合 Swift 协议的泛型类型

swift 的字符串类型是不是符合收集协议?