Swift 最佳實踐之 Optional
Swift 作為現代、高效、安全的編程語言,其背後有很多高級特性為之支撐。
『 Swift 最佳實踐 』系列對常用的語言特性逐個進行介紹,助力寫出更簡潔、更優雅的 Swift 代碼,快速實現從 OC 到 Swift 的轉變。
該系列內容主要包括: - Optional - Enum - Closure - Protocol - Generic - Property Wrapper - Structured Concurrent - Result builder - Error Handle - Advanced Collections (Asyncsequeue/OptionSet/Lazy) - Expressible by Literal - Pattern Matching - Metatypes(.self/.Type/.Protocol)
ps. 本系列不是入門級語法教程,需要有一定的 Swift 基礎
本文是系列文章的第一篇,主要介紹 Optional。
Optional 作為 Swift 使用頻率最高的特性之一,也是安全性的基石。除了常規的 if
、guard
外還有不少高級特性,如:map、flatMap、Optional Pattern 等,通過它們可以寫出更簡潔、優雅的代碼。
Overview
可選類型 (Optional) 可以説是現代高級語言的標配,如:Kotin,Java,Javascript,Dart 等,Swift 也不類外。
在各種支持 Optional 的語言中,相關語法也非常類似。
定義 Optional 類型,最常用的語法是「 Type? 」,如:
let age: Int? = 20
「 Type? 」是語法糖,其完整語法是:Optional<Type>
如上 age
的完整定義是:
let age: Optional<Int> = Optional.some(20)
可以看到,可選類型 Optional
是個「獨立類型」,Int
與 Int?
有着本質的區別,不是一回事。
Optional
```
@frozen public enum Optional
/// The absence of a value.
///
/// In code, the absence of a value is typically written using the `nil`
/// literal rather than the explicit `.none` enumeration case.
case none
/// The presence of a value, stored as `Wrapped`.
case some(Wrapped)
/// Creates an instance that stores the given value.
public init(_ some: Wrapped)
// ...
} ```
如上,Optional
是個泛型 Enum,含有 2 個 case:
-
none
:代表「空」,即 nillet age: Int? = nil
等價於:
let age: Optional<Int> = .none // Optional.none
-
some
:代表「非空」,關聯具體的值let age: Int? = 20
等價於:
let age: Optional<Int> = .some(20)
或:
let age: Optional<Int> = .init(20)
map
實現如下方法,加載 data:
你會怎麼做?
func loadData(url: URL?) -> Data?
首先想到的:
``` func loadData(url: URL?) -> Data? { guard let url else { return nil }
return try? Data.init(contentsOf: url) } ```
還可以寫得更優雅、簡潔些:
func loadData(url: URL?) -> Data? {
try? url.map { try Data.init(contentsOf: $0)}
}
map
?!
沒錯,Optional 實現了 map
方法:
public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U? {
switch self {
case .some(let y):
return .some(try transform(y))
case .none:
return .none
}
}
通過 map
源碼可以看到:
- 若 optional 不為 nil,執行閉包
transform
- 否則返回 nil
flatMap
實現如下方法,將 String 類型的 url 轉換成 URL:
func transformURL(_ url: String?) -> URL?
現學現用,愉快地寫下:
func transformURL(_ url: String?) -> URL? {
url.map { URL(string: $0) }
}
抱歉,編譯錯誤:
Value of optional type 'URL?' must be unwrapped to a value of type 'URL'
原因在於,在方法 transformURL
中,根據類型推演,閉包 map.transform
的返回值類型為 URL
,而非 URL?
那隻能老老實實的:
``` func transformURL(_ url: String?) -> URL? { guard let url else { return nil }
return URL(string: url) } ```
非也!
func transformURL(_ url: String?) -> URL? {
url.flatMap { URL(string: $0) }
}
如上,Optional 還實現了 flatMap
:
public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U? {
switch self {
case .some(let y):
return try transform(y)
case .none:
return .none
}
}
flatMap
與 map
唯一的區別就是其 transform
閉包返回的泛型類型的可選類型
總之,map
、flatMap
是 2 個非常方便的方法,可以寫出更簡潔優雅的代碼。
Equatable
extension Optional : Equatable where Wrapped : Equatable {
public static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l == r
case (nil, nil):
return true
default:
return false
}
}
}
由於 Optional
有條件的 (Wrapped : Equatable
) 實現了 Equatable
協議,故可以對兩個符合條件的 Optional
類型直接進行判等操作
該運算符還可用於 non-optional 與 optional 間,其中 non-optional value 先自動轉換成 optional,再進行判等操作,如:
``` let num: Int = 1 let numOptional: Int? = 1
if num == numOptional { // here! } ```
Optional Pattern
Pattern-Matching Operator (~=),模式匹配運算符,主要用於 case
匹配
由於 Optional 實現了該運算符,故可以通過 case
語句判斷 Optional 是否為 nil
:
func transformURL(_ url: String?) -> URL? {
switch url {
case let url?: // 等價於 case let .some(url):
return URL(string: url)
case nil:
return nil
}
}
上面這種寫法與直接用 if
/guard
判斷相比並沒什麼優勢,更別説與 flatMap
版本相比了。
但對於一些組合的判斷即十分方便,如上面提到的 ==
運算符的實現:
public static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l == r
case (nil, nil):
return true
default:
return false
}
}
如果用 if
判斷改寫:
public static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
if let lhs, let rhs {
return lhs == rhs
}
else if lhs == nil, rhs == nil {
return true
}
else {
return false
}
}
很明顯,if
版本的可讀性比 switch...case
版本差。
case
不僅可以用於 switch
,還可以用於 if
、for
等,如:
``` let numOptional: Int? = 0 if case .none = numOptional { // 等價於 if numOptional == nil // ... }
if case let num? = numOptional { //等價於 if let num = numOptional // ... } ```
如下,打印 scores
中所有及格的分數,你會如何實現?
let scores: [Int?] = [90, nil, 80, 100, 40, 50, 60]
通過 for case let
可以寫出很優雅的代碼:
// 等價於 for case let .some(score) in scores where score >= 60
for case let score? in scores where score >= 60 {
print(score)
}
用函數式形式也可以寫出很優雅的代碼
let passingScores = scores.compactMap { score in score.flatMap { $0 >= 60 ? $0 : nil } }
關於 Pattern-Matching 的詳細介紹將在後續專題文章中展開
if let
shorthand
本文所有關於 if let
的示例是否感覺有點怪,如:
public static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
if let lhs, let rhs {
// ...
}
// ...
}
其中,if let
我們熟悉的寫法是不是:
if let lhs = lhs, let rhs = rhs {
// ...
}
上述 2 種寫法是等價的
第一個版本是 Swift 5.7 實現的新特性,用於簡化「 Optional binding 」:swift-evolution/0345-if-let-shorthand
對於所有條件控制語句都適用:
``` if let foo { ... } if var foo { ... }
else if let foo { ... } else if var foo { ... }
guard let foo else { ... } guard var foo else { ... }
while let foo { ... } while var foo { ... } ```
小結
Optional 本質上是 Enum,因此 Int
與 Int?
有着本質的區別。
Enum Optional 上定義了許多便捷的操作方法,如:map
、flatMap
等,充分利用它們可以寫出更優雅的代碼。
Swift 5.7 簡化了 Optional binding,if let foo = foo {}
--> if let foo {}
。
參考資料
- Swift 最佳實踐之 Closure
- Swift 最佳實踐之 Optional
- Swift 最佳實踐之 Enum
- 深入淺出 Flutter Framework 之自定義渲染型 Widget
- 深入淺出 Flutter Framework 之 RenderObject
- Swift 新併發框架之 Task
- Swift 新併發框架之 actor
- Swift 新併發框架之 Sendable
- Swift 新併發框架之 async/await
- Swift Protocol 背後的故事(上)
- 『碼』出高質量
- 深入淺出 Flutter Framework 之 PipelineOwner
- 深入淺出 Flutter Framework 之 Layer
- 深入淺出 Flutter Framework 之 PaintingContext
- 深入淺出 Flutter Framework 之 Element