swift 具有 throw init 行为的惰性变量

swift lazy var with throw init behavior

我不确定这是一个错误还是它真的应该如何工作?

class A {
    init() throws { }
}

class B {
    lazy var instance = A()
}

此代码使用 XCode 9 和最新的 Swift 版本编译没有错误,并且工作完美,除非 Class A init() 真的抛出,然后 lazy var 是空指针。但是不应该以某种方式不编译这段代码吗?

作为对您问题的回答:

But shouldn't be this code somehow not be compiled?

好吧,在某些时候你的代码片段没有任何问题(因为 - 正如你提到的 - class A init 实际上并没有抛出),所以它可能是 编译没有任何问题。为了更清楚,将其视为与以下情况类似的情况:

let myString: String? = nil
print(myString!) // crashes!

它会被编译得很好!虽然我们都知道它在评估 myString! 时崩溃,但我们确实知道它会导致 运行-time 崩溃,但这并不意味着编译器应该阻止它,因为它在某些时候可能是有效的(例如,如果我们将其声明为 let myString: String? = "Hello");与您的情况类似,它在某些时候可能是有效的-如上所述-。

通常,对于这种情况,我们作为开发人员有责任根据所需的行为来处理它。


针对这个案例,我们可能要问:

“我们如何实现 instance 惰性变量来捕获错误(使用 do-catch 块)?”

实际上,这段代码不会编译:

class B {
    lazy var instance:A = {
        do {
            let myA = try A()
            return myA
        } catch {
            print(error)
        }
    }()
}

抱怨:

Missing return in a closure expected to return 'A'

因为显然到达catch块意味着没有任何东西可以返回。另外,正如您提到的,即使您将其实现为

lazy var instance = A()

您不会得到编译时错误,但是尝试将它用于实际抛出应该会导致 运行 时间错误:

let myB = B()
print(myB.instance) // crash!

我建议解决此问题的方法是将 instance 声明为惰性 可选 变量:

class B {
    lazy var instance:A? = {
        do {
            let myA = try A()
            return myA
        } catch {
            print(error)
        }

        return nil
    }()
}

在这一点上,如果我们假设 A 初始化程序总是抛出,试图访问它:

let myB = B()
print(myB.instance)

应该记录:

caught error

nil

不会造成任何崩溃。否则,它应该可以正常工作,例如:

let myB = B()
myB.instance?.doSomething() // works fine

这确实是一个错误 (SR-7862) – 你不能从 属性 初始化上下文中抛出错误(即使你可以,你也需要在调用前加上前缀 try), 因此编译器应该产生一个错误。

我已经打开了一个拉取请求来解决这个问题 (#17022)。

编辑: 该补丁现已被精心挑选到 4.2 分支,因此它将在 Swift 4.2 和 [=22] 的发布中得到修复=] 10(在发布之前你可以 try a 4.2 snapshot)。