在 Swift 中实现 Failable Initializer 时,为什么要使用 `init!` 而不是 `init?`?
Why would you use `init!` instead of `init?` when implementing a Failable Initializer in Swift?
Swift 文档 Initialization: Failable Initializers 详细说明了如何使用 init?
创建可失败初始化程序,这是一个 returns 和 可选的初始化程序 它初始化的类型。对于所有可选值,它可以是 nil
或非 nil
.
文档还提到你可以使用 init!
创建一个可失败的初始化器,returns 一个 隐式解包的可选 它初始化的类型(见init!Failable Initializer 部分)。这解开可选的包装并表明它 "should" 是非 nil
。如果它是 nil
并且您未经检查就访问它,程序员可能会跳过它,因为它被标记为 "should be non-nil
",将生成运行时错误。
两个问题:
- When/Why 在实现可失败初始化器时,你会使用
init!
而不是 init?
吗?
- 因为
init!
总是 returns 一个隐式解包的可选值 "should" 是非 nil
,你为什么不直接使用 init
而不是init!
?
在绝大多数情况下,您应该使用 init
而不是 init!
。在少数情况下 init!
是必要的。根据我的经验,最常见的情况是 "must succeed" 初始化程序想要调用可失败的初始化程序。考虑这种情况:
struct NotEmpty {
let something: String
init?(something: String) {
guard !something.isEmpty else { return nil }
self.something = something
}
init() {
self.init(something: "unknown")! // <-- This is illegal
}
}
我们知道 init()
会成功,因为我们传递的是非空字符串。但是没有办法用标准的初始值设定项来表达它(而且编译器也无法证明它是真的)。相反,我们需要使用 init!
:
init!() {
self.init(something: "unknown")
}
现在调用者可以将结果视为非可选的(实际上是非可选的),即使根据类型它可能会失败。那将是一个编程错误,你会崩溃。这里的 !
基本上表示 "yeah, I know it could fail, but I promise it never will." 而在 Swift 中, "promise" 表示 "or else please crash."
您可以在 类 中使用它来覆盖可失败的初始化程序并使其成为不可失败的。
在处理隐式解包时必须小心谨慎:它可能很危险,因为它有可能导致应用程序崩溃。
你可以从 init 委托?初始化!反之亦然,你可以覆盖 init?与初始化!反之亦然。
这意味着您可以将一些 init?
转换为 init!
如果您非常确定它不会失败,反之亦然。在文档中有以下示例:
enum TemperatureUnit {
case Kelvin, Celsius, Fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .Kelvin
case "C":
self = .Celsius
case "F":
self = .Fahrenheit
default:
return nil
}
}
}
您可能有一些类似的代码,并且您可以仅使用常量对其进行初始化,而不使用用户输入或其他一些输入源。所以你很确定它不会失败,为什么不让它 init!
?
Swift 文档 Initialization: Failable Initializers 详细说明了如何使用 init?
创建可失败初始化程序,这是一个 returns 和 可选的初始化程序 它初始化的类型。对于所有可选值,它可以是 nil
或非 nil
.
文档还提到你可以使用 init!
创建一个可失败的初始化器,returns 一个 隐式解包的可选 它初始化的类型(见init!Failable Initializer 部分)。这解开可选的包装并表明它 "should" 是非 nil
。如果它是 nil
并且您未经检查就访问它,程序员可能会跳过它,因为它被标记为 "should be non-nil
",将生成运行时错误。
两个问题:
- When/Why 在实现可失败初始化器时,你会使用
init!
而不是init?
吗? - 因为
init!
总是 returns 一个隐式解包的可选值 "should" 是非nil
,你为什么不直接使用init
而不是init!
?
在绝大多数情况下,您应该使用 init
而不是 init!
。在少数情况下 init!
是必要的。根据我的经验,最常见的情况是 "must succeed" 初始化程序想要调用可失败的初始化程序。考虑这种情况:
struct NotEmpty {
let something: String
init?(something: String) {
guard !something.isEmpty else { return nil }
self.something = something
}
init() {
self.init(something: "unknown")! // <-- This is illegal
}
}
我们知道 init()
会成功,因为我们传递的是非空字符串。但是没有办法用标准的初始值设定项来表达它(而且编译器也无法证明它是真的)。相反,我们需要使用 init!
:
init!() {
self.init(something: "unknown")
}
现在调用者可以将结果视为非可选的(实际上是非可选的),即使根据类型它可能会失败。那将是一个编程错误,你会崩溃。这里的 !
基本上表示 "yeah, I know it could fail, but I promise it never will." 而在 Swift 中, "promise" 表示 "or else please crash."
您可以在 类 中使用它来覆盖可失败的初始化程序并使其成为不可失败的。
在处理隐式解包时必须小心谨慎:它可能很危险,因为它有可能导致应用程序崩溃。
你可以从 init 委托?初始化!反之亦然,你可以覆盖 init?与初始化!反之亦然。
这意味着您可以将一些 init?
转换为 init!
如果您非常确定它不会失败,反之亦然。在文档中有以下示例:
enum TemperatureUnit {
case Kelvin, Celsius, Fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .Kelvin
case "C":
self = .Celsius
case "F":
self = .Fahrenheit
default:
return nil
}
}
}
您可能有一些类似的代码,并且您可以仅使用常量对其进行初始化,而不使用用户输入或其他一些输入源。所以你很确定它不会失败,为什么不让它 init!
?