在 Swift 中实现可失败初始化程序的最佳实践 [英] Best practice to implement a failable initializer in Swift

查看:24
本文介绍了在 Swift 中实现可失败初始化程序的最佳实践的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

<块引用>

在从初始化程序返回 nil 之前,必须初始化类实例的所有存储属性.

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

2.我的方法是正确的还是有其他想法或通用模式来实现我的目标?

class 用户:NSObject {让用户名:字符串让 isSuperUser: Bool = false让一些细节:[字符串]?初始化?(字典:NSDictionary){if let value: String = dictionary["user_name"] as?细绳 {用户名 = 值}别的 {返回零}if let value: Bool = dictionary["super_user"] as?布尔{isSuperUser = 值}someDetails = dictionary["some_details"] 作为?大批超级初始化()}}

解决方案

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

<块引用>

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

<小时>

对于 Swift 2.1 及更早版本:

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

<块引用>

然而,对于类,一个可失败的初始化器可以触发一个仅在所有存储属性引入后初始化失败该类已设置为初始值和任何初始值设定项已进行委托.

注意:它实际上适用于结构和枚举,但不适用于类.

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

文档中的示例:

class Product {让名称:字符串!初始化?(名称:字符串){如果 name.isEmpty { 返回 nil }self.name = 姓名}}

<块引用>

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

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

class 用户:NSObject {让用户名:字符串!让 isSuperUser: Bool = false让一些细节:[字符串]?初始化?(字典:NSDictionary){超级初始化()if let value = dictionary["user_name"] as?细绳 {self.userName = 值}别的 {返回零}if let value: Bool = dictionary["super_user"] as?布尔{self.isSuperUser = 值}self.someDetails = dictionary["some_details"] 作为?大批}}

With the following code I try to define a simple model class and it's failable initializer, which takes a (json-) dictionary as parameter. The initializer should return nil if the user name is not defined in the original json.

1. Why doesn't the code compile? The error message says:

All stored properties of a class instance must be initialized before returning nil from an initializer.

That doesn't make sense. Why should I initialize those properties when I plan to return nil?

2. Is my approach the right one or would there be other ideas or common patterns to achieve my goal?

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

解决方案

Update: From the Swift 2.2 Change Log (released March 21, 2016):

Designated class initializers declared as failable or throwing may now return nil or throw an error, respectively, before the object has been fully initialized.


For Swift 2.1 and earlier:

According to Apple's documentation (and your compiler error), a class must initialize all its stored properties before returning nil from a failable initializer:

For classes, however, a failable initializer can trigger an initialization failure only after all stored properties introduced by that class have been set to an initial value and any initializer delegation has taken place.

Note: It actually works fine for structures and enumerations, just not classes.

The suggested way to handle stored properties that can't be initialized before the initializer fails is to declare them as implicitly unwrapped optionals.

Example from the docs:

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

In the example above, the name property of the Product class is defined as having an implicitly unwrapped optional string type (String!). Because it is of an optional type, this means that the name property has a default value of nil before it is assigned a specific value during initialization. This default value of nil in turn means that all of the properties introduced by the Product class have a valid initial value. As a result, the failable initializer for Product can trigger an initialization failure at the start of the initializer if it is passed an empty string, before assigning a specific value to the name property within the initializer.

In your case, however, simply defining userName as a String! does not fix the compile error because you still need to worry about initializing the properties on your base class, NSObject. Luckily, with userName defined as a String!, you can actually call super.init() before you return nil which will init your NSObject base class and fix the compile error.

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

这篇关于在 Swift 中实现可失败初始化程序的最佳实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆