隨著 Apple 推出 Xcode 11.4,Swift 5.2 也正式發佈了。
這次的版本對開發人員可說是福音,因為它著重於改善開發人員的體驗。借助改善了的診斷功能,開發人員可以更快地解決錯誤。現在,程式碼完成功能 (Code completion) 運作得更好,而且程式碼的大小和記憶體使用量也減少了。
在本篇文章中,我們將會快速地看看最新版本語言的改變!讓我們開始吧!
函數式 Key Path Expression
Swift Evolution 提案 SE-0249 推出了一個 Shortcut,讓我們可以在某些特定情況下使用 keypath。
它讓我們在可以使用 (Root) -> Value
函數的地方,能夠做用 Keypath Expression \Root.value
。
讓我們通過下列範例來了解,以下是一個有兩個屬性 (property) 的結構 (struct) User。
struct User {
let firstName: String
let lastName: String
}
我們可以創建一些實例 (instance),並將其放入陣列 (array) 中,如下所示:
let harry = User(firstName: "Harry", lastName: "Potter")
let hermione = User(firstName: "Hermione", lastName: "Granger")
let ron = User(firstName: "Ron", lastName: "Weasley")
let users = [harry, hermione, ron]
現在,如果我們想取得一個包含所有使用者名字 (firstName) 的陣列,我們像這樣做:
let oldFirstNames = users.map { $0.firstName }
但是,有了 Swift 5.2 Keypath,我們就可以這樣做:
let newFirstNames = users.map(\.firstName)
我們也可以在 compactMap
和 filter 上使用它,就像是這樣:
let lastNames = users.compactMap(\.lastName)
使用者定義類型的可呼叫值 (Callable Value)
Swift Evolution 提案 SE-0253 在 Swift 引入了一個「靜態」可呼叫值。可呼叫值是以類似函數行為定義的值,可以被函數 syntax 所呼叫。
讓我們看看以下範例:
struct Adder {
var base: Int
func callAsFunction(_ x: Int) -> Int {
return x + base
}
}
var adder = Adder(base: 3)
adder(10) // returns 13
adder.callAsFunction(10) // returns 13
在範例中可以看到,如果一個值的型別實作了 callAsFunction()
方法,我們就可以直接呼叫該值。
我們可以按需要添加無限多的參數 (parameter),也可以控制回傳值,有需要的話,我們甚至可以將方法標記為 mutating。而且,我們更可以在單個型別上定義多個 callAsFunction 方法。
請注意,callAsFunction 與 Swift 5 推出的 @dynamicCallable 並不一樣:
當我們宣告一個 call-as-function 方法,我們會指定引數 (arguments) 的數量,以及其型別和標籤 (label)。但當我們宣告 dynamicCallable 屬性的方法時,我們只會指定用於保存引數陣列的型別。
Subscript 現在可以宣告預設引數
現在,在添加客製化 subscript 到型別時,我們可以將預設引數用於任何參數。
例如,我們有一個 Hogwarts 結構,並準備要定義客製化 subscript 來取得特登學生資料,當有人嘗試訪問不存在的索引時,我們就可以回傳的一個預設值:
struct Hogwarts {
var students: [String]
subscript(index: Int, default default: String = "Unknown") -> String {
if index >= 0 && index < students.count {
return students[index]
} else {
return `default`
}
}
}
let school = Hogwarts(students: ["Harry", "Hermione", "Ron"])
print(school[0])
print(school[5])
以上結果將會印出在索引 0 的 Harry,而因為索引 5 沒有學生,結果會印出 Unknown。
如果我在 subscript 使用了 default default
,就可以使用這樣的一個客製化值:
print(school[-1, default: "Draco"])
對 Module 外定義的子類別繼承添加限制
在 Swift 5.2,有一個小改變可以會破壞以前的功能。
現在,如果一個超類別 (superclass) 已經在其他 Module 中被定義了,並擁有一個非公開的指定初始器 (non-publice designated initializers),其子類別就無法自動從超類別繼承便捷初始化器 (convenience initializer)。
要恢復這種自動繼承行為,基底類別 (base class) 必須確保所有指定初始器都是 public
或 open
。
Lazy Filtering 的次序倒轉了
另一個可以破壞原本功感的改變,就是 Lazy Filtering。如果我們使用像是 array 的 Lazy Sequence,並應用多個過濾器,現在,這些過濾器將以相反的順序運行。
讓我們看看下面的例子,假設我們有一個名字陣列,我們把第一個我們應用第一個過濾器選擇以 H 開頭的名稱,然後應用第二個過濾器以獲取回傳 true 的名稱。
let people = ["Harry", "Hermione", "Ron"]
.lazy
.filter { $0.hasPrefix("H") }
.filter { print($0); return true }
print(people.count)
在 Swift 5.2 或更新的版本中,結果將印出 “Harry” 和 “Hermione”,因為在第一個過濾器運行之後,只有這兩個名字剩下來,並進入第二個過濾器。但是,在 Swift 5.2 版本之前,結果將返回所有名字,因為第二個過濾器會於第一個過濾器之前運行。
這是因為 Lazy Filtering,如果我們把 .lazy 拿走,無論是甚麼 Swift 版本,它都會印出 “Harry” 和 “Hermione”。
外部的預設值
在 Swift 5.2 之前的版本中,當內部函數 (inner function) 使用外部函數 (outer function) 引數作為默認引數 (SR-2189) 時,編譯器通常會崩潰。
Swift 5.2發佈後,你可以成功運行以下程式碼,否則編譯時就會回傳 unsafeMutableAddressor 錯誤。
func outer(x: Int) -> (Int, Int) {
func inner(y: Int = x) -> Int {
return y
}
return (inner(), inner(y: 0))
}
改善了的新診斷功能
Swift 5.2大大提高了 Swift 編譯器中錯誤訊息的質量和準確性。
舉個例子:
import SwiftUI
struct RoomDetails: View {
@State var roomName: String
@State var imageName: String
var body: some View {
VStack {
TextField("Room Name")// It should be TextField("Name", text: $name)
Image(imageName)
.frame(maxWidth: 300)
}
}
}
在以上的程式碼中,我們嘗試將 TextField
視圖綁定到一個 roomName @State
。在 Swift 5.1,這會導致 frame()
修飾符出現錯誤,說明 “Int” 不能轉換為 “CGFloat”。但是 Swift 5.2 或更新的版本,就可以正確地識別這個錯誤,是呼叫中缺少參數文本的引數。
程式碼完成功能的改善
在程式碼完成功能方面,其結果的信息型別改善了。在可行的情況下,結果會顯示不透明的結果類型(例如 some View
),並保留型別別名。結果將不再顯示非必需的父型別。
例如,在 Swift 5.1.3 (Xcode 11.3.1) 中:
現在,在 Swift 5.2 (Xcode 11.4) 則顯示為:
總結
主要來說,Swift 5.2 改善了開發體驗,讓我們探索其豐富的功能。除了針對編譯器錯誤的新診斷引擎外,這裡還有許多嶄新的測試和除錯功能等著你去探索!