Apple 的宣佈
Apple 於 3 月 25 日宣佈,Swift 5.3 的發佈過程已經開始了,這次的變更十分大,因為新版本將提高整體語言的品質和性能,並使 Swift 支援 Windows 和 Linux 等多個平台。讓我們詳細看看新功能吧!
Enum 可用作 Protocol Witness
現在,一個類別要擴展協定,就需要完全符合協定的要求。舉個例子,如果我們在協定中編寫了靜態 (static) 要求:
protocol DecodingError {
static var fileCorrupted: Self { get }
static func keyNotFound(_ key: String) -> Self
}
然後,如果我們嘗試讓一個列舉遵從協定,就會遇到這樣的錯誤:
有了 Swift Evolution 提案 SE-0280,就不會再出現這樣的編譯錯誤了。
多重尾隨閉包 (Multiple Trailing Closures)
Swift 5.3 終於加入了多重尾隨閉包的功能了!
背景資料
如果你還不知道甚麼是尾隨閉包,它是一個簡單的語法糖 (syntactic sugar),可以在有閉包參數的函數內使用:
func networkCall(parameter:Int, onSuccess:(Any)->Void) {
//network call
onSuccess("someNetworkResult")
}
我們可以如此簡單呼叫這個函數:
networkCall(parameter: 1) { (result) in
print(result)
}
問題
這是一個非常好的功能,讓我們可以把程式碼寫得更簡潔易讀,但是這個功能只限用於函數的最後一個參數上,像是這個例子:
解決方法
有了 SE-0279,我們可以簡單地添加標籤,來加入額外的尾隨閉包,所以我們可以如此編寫程式碼:
networkCall(parameter: 1){(progress) in
print(progress)
}, onSuccess{ (result) in
print(result)
}
新的 Float16 型別
這一點非常簡單,我會以 Apple 的解釋來說明:
在過去十年中,少於 (32-bit) 的浮點型別
Float
的使用量大大增長,當中最廣泛使用的是Float16
,主要用於手機 GPU 計算、作為 HDR 圖像的像素格式、及作為 ML App 中 Weight 的壓縮格式。對於着色器語言程式 (shader-language program),將這個型別引入 Swift 可以大大提升其互操作性 (Interoperability)。因為使用者經常需要在 CPU 上設置數據結構,以傳遞到 GPU 程式。如果 Swift 中可用的型別,它們就要使用不安全的機制來創建這些結構。
你可以到 SE-0277 看看詳細資料。
Catch 字句擷取多於一個錯誤
現在,在 Swift 中,每個 Catch 字句只可以擷取一個錯誤型別,也就是說,如果的們要定義以下列舉:
enum NetworkError: Error {
case timeout, serverError
}
假設 someNetworkCall 就是引發 NetworkError 的函數,而我們想處理這兩個錯誤,就需要編寫兩個單獨的 catch 子句:
func networkCall(){
do{
try someNetworkCall()
}catch NetworkError.timeout{
print("error")
}catch NetworkError.serverError{
print("error")
}
}
有了 SE-0276,我們可以這樣用一個 catch 字句處理兩個錯誤:
func networkCall(){
do{
try someNetworkCall()
}catch NetworkError.timeout, NetworkError.serverError{
print("error")
}
}
Collection Operation 可用於非連續的元素上
現在,我們可以使用 Range<Index> 來引用集合 (collection) 中的多個連續位置,但是根據某些條件,對於不連續的位置,就沒有一個簡單的方法可以做到這一點。
例如,我們有一個從 0 到 15 的數字陣列,如果我們想要獲得這個陣列的子範圍 (sub range),假設子範圍是所有偶數。因為語言沒有提供任何方式來取得這個範圍,也無法獲得元素位置不連續的子陣,所以我們只能選擇連續元素的範圍:
SE-0270 添加了一個超級有用的 “RangeSet” 方法,讓我們可以獲取子範圍,包含符合特定情況的所有元素。所以,如果我們想要從前文的陣列中擷取偶數,可以如此編寫程式碼:
var numbers = Array(1...15)
// Find the indices of all the even numbers
let indicesOfEvens = numbers.subranges(where: { $0.isMultiple(of: 2) })
// Perform an operation with just the even numbers
let sumOfEvens = numbers[indicesOfEvens].reduce(0, +)
// sumOfEvens == 56
// You can gather the even numbers at the beginning
let rangeOfEvens = numbers.moveSubranges(indicesOfEvens, to: numbers.startIndex)
// numbers == [2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15]
// numbers[rangeOfEvens] == [2, 4, 6, 8, 10, 12, 14]
在引用循環不太可能發生時 提高@escaping 閉包中 implicit self 的可用性
現在在閉包中,即使在不太可能發生循環引用 (reference cycle) 的情況下,我們也需要使用 explicit self。例如是因為我們已經在閉包中捕獲了 self:
為了讓我們的程式碼可以編譯,我們需要向 x 添加 explicit self,以對其進行引用。
另一個例子是結構 (struct),當 self 是一種數值型別,就不會導致起引用循環:
有了 SE-0269,在前一個例子中,我們就不需要使用 explicit 了。
完善 didSet 語義
現在,如果我們有一個帶有 “didSet” observer 的屬性,即使 Observer 本身不包含任何對 oldValue 的引用,要取得 oldValue 的 getter 都會被呼叫。看看以下例子:
struct Container {
var items: [Int] = .init(repeating: 1, count: 100) { //creates an Array of 100 elements
didSet {
print("value set")//oldValue never Accessed
}
}
mutating func update() {
for index in 0..<items.count {
items[index] = index + 1
}
}
}
var container = Container()
container.update()
從以上例子可見,即使無用,程式碼還是在記憶體中製造了 100 個陣列的複本,來提供 oldValue
,導致性能問題。
有了 SE-0268,如果 didSet 內沒有出現 oldValue,屬性的 getter 就不會被呼叫。如此一來,就可以提高性能。
class Foo {
var bar = 0 {
didSet { print("didSet called") }
}
var baz = 0 {
didSet { print(oldValue) }
}
}
let foo = Foo()
// This will not call the getter to fetch the oldValue
foo.bar = 1
// This will call the getter to fetch the oldValue
foo.baz = 2
Contextually Generic Declarations 的 Where 子句
現在,我們只能通過將 member 放在特定 extension 內,來使用 member declaration 上的 where 子句。這聽起來可能有點混亂,讓我們舉個例子。
假設我們想擴展陣列實作,以分類 3 種不同型別的元素,所以我們要定義不同的分類邏輯。現在,我們需要將陣列擴展 3 次:
extension Array where Element == String {
func sortStrings() { ... } //some specific sorting logic
}
extension Array where Element == Int {
func sortInts() { ... } //some specific sorting logic
}
extension Array where Element: Equatable {
func sort() { ... } //some specific sorting logic
}
有了 SE-0267,我們只要在一個 extension 中添加 where 子句到函數,就可以實作該邏輯:
extension Array where Element: Equatable {
func sort() { ... }
func sortInts() where Element == Int { ... }
func sortStrings() where Element == String { ... }
}
合成列舉型別 Comparable 的遵從性
假設我們想定義一個列舉,而它有明顯的語義順序,例如:
enum SchoolGrades {
case F
case E
case D
case C
case B
case A
}
如果我們嘗試去比較其數值,我們會得到以下錯誤:
有甚麼問題呢?
為了要將兩個物件作比較,它們需要遵從 Comparable 協定:
看看警告訊息,這會導致一個循環,因為「函數會呼叫自己」,所以我們需要比較 rawValues,才可以比較成績 (SchoolGrade)。
這只是一個很簡單的例子,但也添加了很多樣板程式碼。
有了 SE-0266,我們不需要添加任何程式碼,就可以比較列舉。
Package Manager 的改變
隨著 Swift 版本更新,Swift Package Manager 都會有幾個重大的改善。
Package Manager 資源
SE-0271 在 Swift Package 內引入了資源的支援,包括圖像和數據文件,並讓其易於訪問;而 SE-0278 則添加了資源的本地化支援。
Package Manager Conditional Target Dependencies
SE-0273 讓 Package 作者可以按平台添加特定的依賴關係。
Package Manager 支援 Binary Package
Swift Package Manager 現時只支援 source package,而 SE-0272 就可以支援像是 GoogleAnalytics 的 Binary Package,讓 Swift Package Manager 運行得更快。
你認為最有用的功能是哪一項呢?歡迎留言分享你的意見。謝謝你的閱讀!