提问者:小点点

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


通过下面的代码,我尝试定义一个简单的模型类和它的可失败初始化器,它接受一个(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()
    }
}

共3个答案

匿名用户

这没有意义。当我计划返回nil时,为什么要初始化这些属性?

根据克里斯·拉特纳的说法,这是一个bug。他是这么说的:

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

来源

编辑:

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

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

匿名用户

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

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

对于Swift 2.1及更早版本:

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

然而,对于类,可失败的初始化程序只有在该类引入的所有存储属性都设置为初始值并且进行了任何初始化程序委派之后才能触发初始化失败。

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

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

文档中的示例:

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

在上面的示例中,Product类的name属性被定义为具有隐式解包装的可选字符串类型(String!)。因为它是可选类型,这意味着name属性在初始化期间被分配特定值之前具有默认值nil。这个默认值nil反过来意味着由Product类引入的所有属性都具有有效的初始值。因此,如果传递了一个空字符串,则Product的可失败初始化器可以在初始化器开始时触发初始化失败,然后再将特定值分配给初始化器内的name属性。

然而,在您的情况下,简单地将userName定义为String!并不能修复编译错误,因为您仍然需要担心初始化基类NSObject的属性。幸运的是,将userName定义为String!,您实际上可以在返回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
    }
}

匿名用户

我接受Mike S的回答是Apple的建议,但我不认为这是最佳实践。强类型系统的全部意义在于将运行时错误转移到编译时。这种“解决方案”违背了这一目的。IMHO,更好的做法是继续并将用户名初始化为",然后在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
        }
    }
}