Swift init from unknown class 符合协议

Swift init from unknown class which conforms to protocol

我目前正在将一个大型项目从 Objective-C 更新到 Swift,但我对如何模仿某些逻辑感到困惑。基本上我们有一个 class 和一个协议,该协议定义了几个函数来将任何 class 转换为自身的 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 中的一个函数 class 为符合协议的 NSDictionary 中的每个对象运行 convertToJSON 函数,另一个函数则相反,创建对象的实例与初始化功能。输出字典还包含两个键,一个表示所讨论的字典支持此协议 (kJsonSupport: BOOL),另一个包含对象从 (kJsonClass: NSString 转换而来的 class 的 NSString 表示形式). reverse 函数然后使用这两个来确定对象是从什么 class 转换而来的,以从给定的字典中初始化一个新实例。

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

这是它在 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 行都遇到了一些错误,例如:

Non-protocol, non-class type 'AnyClass' (aka 'AnyObject.Type') cannot be used within a protocol-constrained type

'init' is a member of the type; use 'type(of: ...)' to initialize a new object of the same dynamic type

Argument type 'NSDictionary' does not conform to expected type 'JsonProtocol'

Extraneous argument label 'json:' in call

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

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

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

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

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

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

这确保 class 的名称是 Person(就像它在 ObjC 中一样)而不是 MyGreatApp.Person(这是通常在 [=51 中的名称) =]).

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

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