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

Kai*_*ann 98 object-initializers swift

使用以下代码,我尝试定义一个简单的模型类,它是可用的初始化器,它将(json-)字典作为参数.nil如果未在原始json中定义用户名,则应返回初始化程序.

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()
    }
}
Run Code Online (Sandbox Code Playgroud)

mus*_*afa 131

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

根据Chris Lattner的说法,这是一个错误.他说的是这样的:

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

资源

编辑:

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

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

  • 仅供参考:_"确实.这仍然是我们想要改进的东西,但并没有为Swift 1.2做好准备." - Chris Lattner 10. 2015年2月 (22认同)
  • 仅供参考:在Swift 2.0 beta 2中,这仍然是一个问题,它也是一个抛出初始化程序的问题. (14认同)
  • 感谢我的观点,即初始化不再需要的属性的想法似乎不太合理.和+1共享一个来源,证明克里斯拉特纳感觉像我一样;). (2认同)

Mik*_*e S 69

更新:来自Swift 2.2更改日志(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
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,Product类的name属性被定义为具有隐式解包的可选字符串类型(String!).因为它是可选类型,这意味着在初始化期间为name属性分配特定值之前,name属性的默认值为nil.此默认值nil反过来意味着Product类引入的所有属性都具有有效的初始值.因此,如果在初始化程序中为name属性指定特定值之前传递空字符串,那么Product的初始化程序可以在初始化程序启动时触发初始化失败.

但是,在您的情况下,简单地定义userNameString!不会修复编译错误,因为您仍然需要担心初始化基类的属性,NSObject.幸运的是,userName定义为a 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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 在swift1.2中,来自文档的示例产生错误"必须在从初始化程序返回nil之前初始化类实例的所有存储属性" (9认同)
  • @jeffrey这是正确的,文档(`Product`类)中的示例在分配特定值之前不能触发初始化失败,即使文档说它可以.文档与最新的Swift版本不同步.建议现在改为`var`而不是`let`.[来源:Chris Lattner](https://groups.google.com/forum/#!topic/swift-language/78A0i1vDasc). (2认同)

Dan*_* T. 7

我接受Mike S的回答是Apple的建议,但我不认为这是最好的做法.强类型系统的重点是将运行时错误移动到编译时.这种"解决方案"违背了这一目的.恕我直言,更好的是继续初始化用户名"",然后在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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 经过审核,我发现你是对的,但只是因为userName是一个常量.如果它是一个变量,那么接受的答案会比我的更差,因为userName可能会稍后设置为nil. (2认同)

Kev*_*n R 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
    }
}
Run Code Online (Sandbox Code Playgroud)

使用它将成为:

if let user = User.fromDictionary(someDict) {

     // Party hard
}
Run Code Online (Sandbox Code Playgroud)