在Swift中实现failable初始化器的最佳实践

使用下面的代码,我尝试定义一个简单的模型类,它是一个failable初始值设定项,它以(json-)字典作为参数。 如果在原始json中没有定义用户名,则初始化程序应返回nil

1.为什么代码不能编译? 错误消息说:

所有存储的类实例的属性必须在初始化程序返回nil之前进行初始化。

这没有意义。 为什么我应该在计划返回nil初始化这些属性?

2.我的方法是正确的,还是会有其他的想法或共同的模式来实现我的目标?

 class User: NSObject { let userName: String let isSuperUser: Bool = false let someDetails: [String]? init?(dictionary: NSDictionary) { if let value: String = dictionary["user_name"] as? String { userName = value } else { return nil } if let value: Bool = dictionary["super_user"] as? Bool { isSuperUser = value } someDetails = dictionary["some_details"] as? Array super.init() } } 

更新:从Swift 2.2更改日志 (2016年3月21日发布):

声明为failable或throwing的指定类初始化器现在可能分别在对象完全初始化之前返回nil或抛出错误。


对于Swift 2.1及更早版本:

根据苹果的文档(和你的编译器错误),一个类必须初始化其所有存储的属性,然后从一个failable初始化程序返回nil

然而,对于类,只有在该类引入的所有存储属性都被设置为初始值并且发生了任何初始化代理后,才能触发初始化失败。

注意:它实际上适用于结构和枚举,而不是类。

处理初始化器失败之前无法初始化的存储属性的build议方法是将它们声明为隐式解包的可选项。

来自文档的示例:

 class Product { let name: String! init?(name: String) { if name.isEmpty { return nil } self.name = name } } 

在上面的例子中,Product类的name属性定义为具有隐式解包的可选stringtypes(String!)。 由于它是一个可选types,这意味着name属性在初始化过程中被赋予一个特定的值之前,其默认值为nil。 这个nil的默认值意味着由Product类引入的所有属性都有一个有效的初始值。 因此,如果在初始化程序中为名称属性指定特定值,则可以在初始化程序启动时通过空string初始化失败。

在你的情况下,但是,简单地将userName定义为String! 不修复编译错误,因为你仍然需要担心初始化基类NSObject的属性。 幸运的是,将userName定义为一个String! ,你可以在return nil之前实际调用super.init() ,这会启动你的NSObject基类并修正编译错误。

 class User: NSObject { let userName: String! let isSuperUser: Bool = false let someDetails: [String]? init?(dictionary: NSDictionary) { super.init() if let value = dictionary["user_name"] as? String { self.userName = value } else { return nil } if let value: Bool = dictionary["super_user"] as? Bool { self.isSuperUser = value } self.someDetails = dictionary["some_details"] as? Array } } 

这没有意义。 为什么我应该在计划返回零时初始化这些属性?

根据克里斯·拉特纳这是一个错误。 这是他所说的:

这是发行说明中logging的swift 1.1编译器的实现限制。 编译器目前无法在所有情况下销毁部分初始化的类,因此它不允许形成必须的情况。 我们认为这是一个在未来版本中修复的错误,而不是一个function。

资源

编辑:

所以swift现在是开源的,根据这个更新日志现在已经在swift 2.2的快照中修复了

声明为failable或throwing的指定类初始化器现在可能分别在对象完全初始化之前返回nil或抛出错误。

我接受Mike S的回答是苹果的build议,但我不认为这是最好的做法。 强types系统的要点是将运行时错误移动到编译时间。 这个“解决scheme”违背了这个目的。 恕我直言,最好是继续前进,初始化用户名为"" ,然后在super.init()后检查它。 如果允许使用空白的用户名,则设置一个标志。

 class User: NSObject { let userName: String = "" let isSuperUser: Bool = false let someDetails: [String]? init?(dictionary: [String: AnyObject]) { if let user_name = dictionary["user_name"] as? String { userName = user_name } if let value: Bool = dictionary["super_user"] as? Bool { isSuperUser = value } someDetails = dictionary["some_details"] as? Array super.init() if userName.isEmpty { return nil } } } 

另一种规避限制的方法是使用类function来进行初始化。 你甚至可能想把这个function移到扩展名上:

 class User: NSObject { let username: String let isSuperUser: Bool let someDetails: [String]? init(userName: String, isSuperUser: Bool, someDetails: [String]?) { self.userName = userName self.isSuperUser = isSuperUser self.someDetails = someDetails super.init() } } extension User { class func fromDictionary(dictionary: NSDictionary) -> User? { if let username: String = dictionary["user_name"] as? String { let isSuperUser = (dictionary["super_user"] as? Bool) ?? false let someDetails = dictionary["some_details"] as? [String] return User(username: username, isSuperUser: isSuperUser, someDetails: someDetails) } return nil } } 

使用它将成为:

 if let user = User.fromDictionary(someDict) { // Party hard } 

尽pipeSwift 2.2已经发布了,你不必在初始化器失败之前完全初始化这个对象,但是你需要等到https://bugs.swift.org/browse/SR-704被修复。;

我发现这可以在Swift 1.2中完成

有一些条件:

  • 所需的属性应声明为隐式解包选项
  • 只需将一个值分配给您所需的属性。 这个值可能是零。
  • 然后调用super.init()如果你的类是从另一个类inheritance的。
  • 所有您需要的属性被分配一个值后,检查它们的值是否与预期的一样。 如果不是,则返回零。

例:

 class ClassName: NSObject { let property: String! init?(propertyValue: String?) { self.property = propertyValue super.init() if self.property == nil { return nil } } } 

值types(即结构或枚举)的failable初始值设定项可以在其初始化程序实现中的任何点触发初始化失败

然而,对于类,只有在该类引入的所有存储属性都被设置为初始值并且发生了任何初始化代理后,才能触发初始化失败。

摘录自:苹果公司“ Swift编程语言。 “iBooks。 https://itun.es/sg/jEUH0.l

你可以使用方便的init

 class User: NSObject { let userName: String let isSuperUser: Bool = false let someDetails: [String]? init(userName: String, isSuperUser: Bool, someDetails: [String]?) { self.userName = userName self.isSuperUser = isSuperUser self.someDetails = someDetails } convenience init? (dict: NSDictionary) { guard let userName = dictionary["user_name"] as? String else { return nil } guard let isSuperUser = dictionary["super_user"] as? Bool else { return nil } guard let someDetails = dictionary["some_details"] as? [String] else { return nil } self.init(userName: userName, isSuperUser: isSuperUser, someDetails: someDetails) } }