在Swift中实现可用初始化程序的最佳实践

时间:2014-10-21 20:21:40

标签: swift object-initializers

使用以下代码,我尝试定义一个简单的模型类及其可用的初始化程序,它将(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()
    }
}

8 个答案:

答案 0 :(得分:131)

  

这没有意义。我为什么要在何时初始化这些属性   我计划退回nil?

根据Chris Lattner的说法,这是一个错误。以下是他所说的:

  

这是swift 1.1编译器的一个实现限制,   在发行说明中记录。编译器目前无法   在所有情况下都会破坏部分初始化的类,所以它不允许   形成必须的情况。我们认为这是一个   在将来的版本中修复bug,而不是功能。

Source

修改

所以swift现在是开源的,根据this changelog它现在已经修复了swift 2.2的快照

  

在对象完全初始化之前,声明为failable或throw的指定类初始值设定项现在可以分别返回nil或抛出错误。

答案 1 :(得分:67)

更新:来自Swift 2.2 Change Log(2016年3月21日发布):

  

在对象完全初始化之前,声明为failable或throw的指定类初始值设定项现在可以分别返回nil或抛出错误。


对于Swift 2.1及更早版本:

根据Apple的文档(以及您的编译器错误),类必须在从可用的初始化程序返回nil之前初始化其所有存储的属性:

  

但是,对于类,可用的初始化程序可以触发   只有在引入所有存储属性后才初始化失败   该类已设置为初始值和任何初始化程序   代表团已经举行。

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

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

来自文档的示例:

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

在上面的示例中,Product类的name属性是   定义为具有隐式展开的可选字符串类型   (串!)。因为它是可选类型,这意味着名称   property在分配特定值之前的默认值为nil   初始化期间的值。此默认值nil依次表示   Product类引入的所有属性都有   有效的初始值。因此,适用于Product的初始化程序   可以在初始化程序启动时触发初始化失败   如果在为特定值分配之前传递空字符串   初始化程序中的name属性。

但是,在您的情况下,仅将userName定义为String!并不能解决编译错误,因为您仍需要担心初始化基类NSObject的属性。幸运的是,将userName定义为String!,您实际上可以在super.init()之前调用return nil,它将初始化您的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
    }
}

答案 2 :(得分:7)

我接受Mike S的回答是Apple的建议,但我不认为这是最佳做法。强类型系统的重点是将运行时错误移动到编译时。这个"解决方案"打败那个目的。恕我直言,更好的是继续将用户名初始化为"",然后在super.init()之后进行检查。如果允许空白userNames,则设置一个标志。

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
        }
    }
}

答案 3 :(得分:6)

规避限制的另一种方法是使用类函数进行初始化。 您甚至可能希望将该功能移动到扩展名:

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
}

答案 4 :(得分:3)

虽然Swift 2.2已经发布,您不再需要在初始化程序失败之前完全初始化对象,但您需要保留匹配,直到https://bugs.swift.org/browse/SR-704被修复。

答案 5 :(得分:1)

我发现可以在Swift 1.2中完成

有一些条件:

  • 必须将属性声明为隐式解包的选项
  • 仅为您的所需属性分配一次值。该值可能为零。
  • 如果您的类继承自另一个类,则调用super.init()。
  • 后,所有必需的属性都已分配了值,请检查其值是否符合预期。如果没有,请返回nil。

示例:

class ClassName: NSObject {

    let property: String!

    init?(propertyValue: String?) {

        self.property = propertyValue

        super.init()

        if self.property == nil {
            return nil
        }
    }
}

答案 6 :(得分:0)

  

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

     

但是,对于类,可用的初始化程序可以触发   只有在引入所有存储属性后才初始化失败   该类已设置为初始值和任何初始化程序   代表团已经举行。

摘自:Apple Inc.“ Swift编程语言。”iBooks。 https://itun.es/sg/jEUH0.l

答案 7 :(得分:0)

您可以使用便利初始

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)
    } 
}