Swift 中的可选类型详解:你需要知道的 5 件事
Swift 中的可选类型详解:你需要知道的 5 件事
可选类型是 Swift 的核心,自 Swift 的第一个版本以来就已存在。可选值允许我们编写简洁的代码,同时处理可能的 nil 值。
如果你刚接触 Swift,你应该习惯在属性中添加问号的语法。一旦你习惯了它们,你就可以开始从它们中受益,例如使用扩展。
什么是 Swift 中的可选值?
在我们深入了解你应该知道的知识点列表之前,首先了解基础知识很重要。
属性、方法和下标可以返回一个可选类型,这基本上意味着它要么返回一个值(如果存在),要么返回 nil。可以将多个查询链接在一起,这称为“可选链式”。这是“强制解包”的替代方法,稍后将对此进行更详细的解释。
以下代码示例定义了一个可选的 String,并使用链式打印字符数。
let name: String? = "Antoine van der Lee"
print(name?.count ?? 0)
注意:稍后将解释 ?? 运算符(空合并运算符)。
1. 强制解包 Swift 中的可选类型
强制解包要么返回存在的值,要么在值是 nil 时触发运行时错误。
但在我们深入了解强制解包之前,让我们先了解一下不使用强制解包的解包可能性。
如何解包一个可选类型?
有许多方法可以在 Swift 中解包一个值。你可以使用 guard 语句:
let name: String? = "Antoine van der Lee"
guard let unwrappedName = name else {
return
}
print(unwrappedName.count)
或者你可以使用 if let 语句:
let name: String? = "Antoine van der Lee"
if let unwrappedName = name {
print(unwrappedName.count)
}
自 SE-0345 以来,我们还可以使用简写语法来解包同名属性:
let name: String? = "Antoine van der Lee"
if let name {
print(name.count)
}
或者你可以使用双问号运算符,也称为空合并运算符。这将返回可选值(如果存在)或默认值,在本例中定义为零:
let name: String? = "Antoine van der Lee"
print(name?.count ?? 0)
使用感叹号 (!) 强制解包
可以使用值后直接的感叹号 (!) 强制解包一个可选类型:
var name: String? = "Antoine van der Lee"
print(name!.count)
如果上述示例中的 name 变量被设置为 nil,它将导致以下致命运行时错误:
致命错误:意外发现 nil,同时解包一个可选值
因此,意识到你处于控制之中并且通过使用强制解包冒着崩溃的风险非常重要。根据我的经验,如果可能的话,最好不要使用强制解包。
解包可以链接
可选链式可以这样完成:
struct BlogPost {
let title: String?
}
let post: BlogPost? = BlogPost(title: "Learning everything about optionals")
print(post?.title?.count ?? 0)
强制解包也是如此:
let post: BlogPost? = BlogPost(title: "Learning everything about optionals")
print(post!.title!.count)
但请注意,如果你只解包最后一个可选类型,你仍然会得到一个可选类型。在以下示例中,我们只解包 title,而不解包 post。这意味着如果 post 是 nil,我们仍然无法获得 title:
let post: BlogPost? = BlogPost(title: "Learning everything about optionals")
print(post?.title!.count) // 打印:Optional(35)
可选类型作为最佳实践,强制解包以捕获编程错误
最好不要在不需要时使用感叹号。有些人甚至建议启用 强制解包 SwiftLint 规则。这将防止你引入许多意外崩溃。
但是,一些开发人员更喜欢在值是 nil 时使用强制解包,因为这是一个编程错误。因此,你可以通过强制解包和在早期阶段捕获错误来帮助自己进行调试。我的偏好是尽量不使用强制解包。
2. 可选类型是一个包含两个案例的枚举
很高兴知道可选类型基本上是一个包含两个案例的枚举:
enum Optional<Wrapped> {
/// 值的缺失。
case none
/// 值的存在,存储为 `Wrapped`。
case some(Wrapped)
}
但是,你将使用 nil 来表示值的缺失,而不是使用 .none 案例。
考虑到这一点,你可以使用枚举来定义上述 name 变量:
let name = Optional.some("Antoine van der Lee")
print(name!.count)
或者你可以像使用普通枚举一样使用 switch-case:
func printName(_ name: String?) {
switch name {
case .some(let unwrappedValue):
print("Name is \(unwrappedValue)")
case .none:
print("Name is nil")
}
}
printName(nil) // 打印:"Name is nil"
printName("Antoine van der Lee") // 打印:"Name is Antoine van der Lee"
并且查看其 文档,你可以看到可选类型附带了 一些非常方便的方法。一个很好的例子是 map 方法:
let sideLength: Int? = Int("20")
let possibleSquare = sideLength.map { $0 * $0 }
print(possibleSquare) // 打印:"Optional(400)"
或者 flatMap 方法,在本例中,它仅在名称通过至少包含 5 个字符的验证后才返回名称:
var name: String? = "Antoine van der Lee"
let validName = name.flatMap { name -> String? in
guard name.count > 5 else { return nil }
return name
}
print(validName) // 打印:"Optional("Antoine van der Lee")"
如果你想了解更多有关 map、flatMap 和 compactMap 之间差异的信息,请查看我的博客文章: CompactMap 与 flatMap:差异详解
