Swift 5.3 新功能預覽 大大提高整體語言的品質和性能


本篇原文(標題:What’s New in Swift 5.3 )刊登於作者 Medium,由 Ilario Salatino 所著,並授權翻譯及轉載。

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
}

然後,如果我們嘗試讓一個列舉遵從協定,就會遇到這樣的錯誤:

Enum Cases as Protocol Witnesses

 有了 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) 

}

問題

這是一個非常好的功能,讓我們可以把程式碼寫得更簡潔易讀,但是這個功能只限用於函數的最後一個參數上,像是這個例子:

Multiple Trailing Closures

解決方法

有了 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),假設子範圍是所有偶數。因為語言沒有提供任何方式來取得這個範圍,也無法獲得元素位置不連續的子陣,所以我們只能選擇連續元素的範圍:

Collection Operation

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:

implicit self in @escaping closure -1

為了讓我們的程式碼可以編譯,我們需要向 x 添加 explicit self,以對其進行引用。

另一個例子是結構 (struct),當 self 是一種數值型別,就不會導致起引用循環:

implicit self in @escaping closure -2

有了 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
}

如果我們嘗試去比較其數值,我們會得到以下錯誤:

swift-5.3-1

有甚麼問題呢?

為了要將兩個物件作比較,它們需要遵從 Comparable 協定:

swift-5.3-2

看看警告訊息,這會導致一個循環,因為「函數會呼叫自己」,所以我們需要比較 rawValues,才可以比較成績 (SchoolGrade)。

swift-5.3-3

這只是一個很簡單的例子,但也添加了很多樣板程式碼。

有了 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 運行得更快。

你認為最有用的功能是哪一項呢?歡迎留言分享你的意見。謝謝你的閱讀!

本篇原文(標題:What’s New in Swift 5.3)刊登於作者 Medium,由 Ilario Salatino 所著,並授權翻譯及轉載。

作者簡介:Ilario Salatino,來自意大利的 Mobile Developer,居於柏林。歡迎在 LinkedinInstagram 與我聯絡。

譯者簡介:Kelly Chan-AppCoda 編輯小姐。


此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。

blog comments powered by Disqus
Shares
Share This