Swift 5.3 新特性

譯自 www.hackingwithswift.com/articles/21…git

更多內容,歡迎關注公衆號 「Swift花園」github

喜歡文章?不如來個 🔺💛➕三連?關注專欄,關注我 🚀🚀🚀編程

Swift 5.3 有很多變化,這其中包括多模式 catch 語句,多拖尾閉包,以及 Swift Package Manager 的一些重要改變。swift

本文會帶你瀏覽一些主要的變化,同時提供參考代碼,以便你能夠自行嘗試。如下是要介紹的新特性的清單:數組

  • 多模式 catch 語句
  • 多拖尾閉包
  • 爲枚舉自動生成的 Comparable 實現
  • self. 書寫省略
  • 基於類型的程序入口
  • 基於上下文泛型聲明的 where 語句
  • 枚舉的 cases 能夠做爲 protocol witnesses
  • 從新提煉的 didSet 語義
  • 新的 Float16 類型
  • Swift Package Manager 支持二進制依賴,資源等更多類型

多模式 catch 語句

SE-0276 引入了一個能夠在單個 catch 塊中捕獲多個錯誤 case 的特性,這能讓咱們免除錯誤處理時的重複代碼。安全

例如,下面的代碼用枚舉定義了錯誤的兩種狀況:微信

enum TemperatureError: Error {
    case tooCold, tooHot
}
複製代碼

當咱們讀取到溫度時,既能夠拋出兩種錯誤中的某一個,也能夠返回 「OK」:閉包

func getReactorTemperature() -> Int {
    90
}

func checkReactorOperational() throws -> String {
    let temp = getReactorTemperature()

    if temp < 10 {
        throw TemperatureError.tooCold
    } else if temp > 90 {
        throw TemperatureError.tooHot
    } else {
        return "OK"
    }
}
複製代碼

在捕獲錯誤的環節,SE-0276 容許咱們用逗號分隔來表示咱們要以相同方式處理 tooHottooColdapp

do {
    let result = try checkReactorOperational()
    print("結果: \(result)")
} catch TemperatureError.tooHot, TemperatureError.tooCold {
    print("關閉反應堆")
} catch {
    print("未知錯誤")
}
複製代碼

處理的 case 能夠是任意數量的。框架

多拖尾閉包

SE-0279 引入了多拖尾閉包,這使得調用包含多個閉包的函數能夠更簡單地實現。

這個特性在 SwiftUI 中很是受歡迎。原來形以下面這樣的代碼:

struct OldContentView: View {
    @State private var showOptions = false

    var body: some View {
        Button(action: {
            self.showOptions.toggle()
        }) {
            Image(systemName: "gear")
        }
    }
}
複製代碼

能夠被改寫成:

struct NewContentView: View {
    @State private var showOptions = false

    var body: some View {
        Button {
            self.showOptions.toggle()
        } label: {
            Image(systemName: "gear")
        }
    }
}
複製代碼

理論上並不要求 label: 要跟在前一個閉包的 } 後面,因此你甚至能夠像下面這樣書寫:

struct BadContentView: View {
    @State private var showOptions = false

    var body: some View {
        Button {
            self.showOptions.toggle()
        }

        label: {
            Image(systemName: "gear")
        }
    }
}
複製代碼

不過,我會建議你小心代碼的可讀性 —— 像上面的 label 那樣的代碼塊,在 Swift 裏看起來更像是標籤化的代碼塊,而不是 Button 構造器的第二個參數。

注: 有關 Swift 的多拖尾閉包特性的討論很是熱烈。我想提醒你們的是,像這種類型的語法變更一開始看起來可能會有點彆扭,咱們須要耐心,給它時間,在實踐中體會它帶來的結果。

爲枚舉自動生成的 Comparable 實現

SE-0266 使得咱們能夠爲枚舉生成 Comparable 實現,同時不要求咱們聲明關聯值,或者要求關聯值自己必須是 Comparable 的。這個特性讓咱們能夠在同類型的枚舉之間用 <> 和相似的比較操做符來進行比較。

例如,假設咱們有一個枚舉,它描述了衣服的尺寸,咱們能夠要求 Swift 爲它自動生成 Comparable 實現,代碼以下:

enum Size: Comparable {
    case small
    case medium
    case large
    case extraLarge
}
複製代碼

而後咱們就能夠建立兩個這個枚舉的實例,而且用 < 進行比較:

let shirtSize = Size.small
let personSize = Size.large

if shirtSize < personSize {
    print("T恤 過小了!")
}
複製代碼

自動生成的實現,也能很好地適應枚舉的 Comparable 關聯值。例如,假設咱們有一個枚舉,描述了某個隊伍獲取世界盃冠軍的次數,代碼能夠這樣實現:

enum WorldCupResult: Comparable {
    case neverWon
    case winner(stars: Int)
}
複製代碼

而後咱們用不一樣的值來建立枚舉的不一樣實例,而且讓 Swift 對它們進行排序:

let americanMen = WorldCupResult.neverWon
let americanWomen = WorldCupResult.winner(stars: 4)
let japaneseMen = WorldCupResult.neverWon
let japaneseWomen = WorldCupResult.winner(stars: 1)

let teams = [americanMen, americanWomen, japaneseMen, japaneseWomen]
let sortedByWins = teams.sorted()
print(sortedByWins)
複製代碼

排序過程會把未得到世界盃冠軍的隊伍放在前面,而後是日本女子隊,再而後是美國女子隊 —— 兩組 winner 的隊被認爲是大於兩組 neverWon 的隊,而 winner(stars: 4) 被認爲是大於 winner(stars: 1)

self. 書寫省略

SE-0269 使得咱們能夠在一些沒必要要的地方省略 self 。在這個改變以前,咱們須要在全部的閉包當中對引用 self 的屬性或者方法冠以 self.,以便顯式地明確語義。但有的時候因爲閉包不可能產生引用循環,self 是多餘的。

例如,以前咱們須要把代碼寫成下面這樣:

struct OldContentView: View {
    var body: some View {
        List(1..<5) { number in
            self.cell(for: number)
        }
    }

    func cell(for number: Int) -> some View {
        Text("Cell \(number)")
    }
}
複製代碼

self.cell(for:) 的調用不會產生引用循環,由於它是在結構體內使用。多虧了 SE-0269,上面的代碼如今能夠免去 self.

struct NewContentView: View {
    var body: some View {
        List(1..<5) { number in
            cell(for: number)
        }
    }

    func cell(for number: Int) -> some View {
        Text("Cell \(number)")
    }
}
複製代碼

這個特性對於大量使用閉包的框架很是有用,包括 SwiftUI 和 Combine。

基於類型的程序入口

SE-0281 引入了一個新的 @main 屬性,它可讓咱們聲明程序的入口。這個特性使得咱們能夠精確地控制程序啓動時要執行的代碼,對於命令行程序尤爲有幫助。

例如,當咱們建立一個終端應用時,咱們必須建立一個叫 main.swift 的文件,而後把啓動代碼放在裏面:

struct OldApp {
    func run() {
        print("Running!")
    }
}

let app = OldApp()
app.run()
複製代碼

Swift 會自動把 main.swift 看做最頂層的代碼,建立 App 實例而且運行。即使在 SE-0281 以後這個作法都一直被延續,但如今你能夠幹掉 main.swift 了,轉而使用 @main 屬性來標記某個包含靜態 main 方法的結構體或者類,讓它充當程序入口:

@main
struct NewApp {
    static func main() {
        print("Running!")
    }
}
複製代碼

上面的代碼所在的程序運行時,Swift 會自動調用 NewApp.main() 來啓動程序流程。

新的 @main 屬性對於 UIKit 和 AppKit 開發者來講可能有點屬性,由於咱們正是用 @UIApplicationMain@NSApplicationMain 來標記 app 代理的。

不過,使用 @main 的時候有一些注意事項:

  • 已經有 main.swift 文件的 app 不能使用這個屬性
  • 不能有一個以上的 @main 屬性
  • @main 屬性只能用在最頂層的類型上 —— 這個類型不繼承自任何其餘類

基於上下文泛型聲明的 where 語句

SE-0267 引入一個新特性,你能夠給泛型類型或者擴展添加帶有 where 語句限定的函數。

例如,咱們建立了一個簡單的 Stack,能夠壓棧,出棧元素:

struct Stack<Element> {
    private var array = [Element]()

    mutating func push(_ obj: Element) {
        array.append(obj)
    }

    mutating func pop() -> Element? {
        array.popLast()
    }
}
複製代碼

藉助 SE-0267,咱們如今能夠添加一個 sorted() 方法給這個 Stack,而且要求這個方法只有在 Stack 的泛型參數 Element 遵循 Comparable 協議的時候才能使用:

extension Stack {
    func sorted() -> [Element] where Element: Comparable {
        array.sorted()
    }
}
複製代碼

枚舉的 cases 能夠做爲 protocol witnesses

SE-0280 使得枚舉能夠參與 protocol witness matching,這是一種表述咱們能夠更容易地匹配協議要求的技術方式。

例如,你能夠編寫代碼處理各類類型的數據,可是假如數據不見了怎麼辦呢?固然,你能夠藉助空合運算符,每次都提供一個默認值。不過。你能夠藉助協議來要求默認值,而後讓各類類型遵循這個協議:

protocol Defaultable {
    static var defaultValue: Self { get }
}

// 讓整數有默認值 0
extension Int: Defaultable {
    static var defaultValue: Int { 0 }
}

// 讓數組有默認值空數組
extension Array: Defaultable {
    static var defaultValue: Array { [] }
}

// 讓字典有默認值空字典
extension Dictionary: Defaultable {
    static var defaultValue: Dictionary { [:] }
}
複製代碼

SE-0280 使得咱們能對枚舉作出同樣的控制。好比,你有一個 padding 枚舉,它能接收像素值,釐米值,或者是系統的默認值:

enum Padding: Defaultable {
    case pixels(Int)
    case cm(Int)
    case defaultValue
}
複製代碼

這樣的代碼在 SE-0280 以前是沒法實現的 —— Swift 會抱怨 Padding 不知足協議。可是,若是你仔細琢磨一下,協議實際上是知足的:咱們須要一個靜態的 defaultValue,它返回 Self,換言之,就是某個遵循協議的具體類型,而這正是 Padding.defaultValue 提供的。

從新提煉的 didSet 語義

SE-0268 調整了 didSet 屬性觀察者的工做方式,以便它們能更高效地工做。對於這個優化你不須要改動任何代碼,自動得到一個小小的性能提高。

在內部,Swift 作出的改變是在設置新值時再也不查詢舊值。若是你不使用舊值,也沒有設置 willSet,Swift 會即時修改數值。

假如你須要用到舊值,只須要引用 oldValue 便可,方式以下:

didSet {
    _ = oldValue
}
複製代碼

新的 Float16 類型

SE-0277 引入了一個新的半精度浮點類型,Float16。這個精度在圖像編程和機器學習中十分常見。

新類型和 Swift 原來的其餘類型類似:

let first: Float16 = 5
let second: Float32 = 11
let third: Float64 = 7
let fourth: Float80 = 13
複製代碼

Swift Package Manager 支持二進制依賴,資源等更多類型

Swift 5.3 爲 Swift Package Manager (SPM) 帶來了不少提高,恕我不能在這裏一一舉例。不過咱們能夠討論一下SPM 有哪些變化以及爲何會有這些變化。

首先,SE-0271 (Package Manager Resources) 使得 SPM 能包含諸如圖片,音頻,JSON 等類型的資源。這個機制可不僅是把文件拷進最終的 app bundle 這麼簡單 —— 舉個例子,咱們能夠應用一個自定義處理步驟到咱們的 assets,好比爲 iOS 優化圖片。爲此,新增的 Bundle.module 屬性就是用來在運行時訪問這些 assets 的。SE-0278 (Package Manager Localized Resources) 進一步支持了資源的本地化版本,例如提供適用某個國家的圖片。

其次,SE-0272 (Package Manager Binary Dependencies) 使得 SPM 可使用二進制包。這意味着像 Firebase 這樣的閉源 SDK 如今也能夠經過 SPM 集成了。

再次,SE-0273 (Package Manager Conditional Target Dependencies) 可讓咱們指定爲特定平臺和配置使用依賴。例如,咱們可能在爲 Linux 平臺編譯時,額外須要某些特定的框架,或者咱們在本地測試的時候須要一些依賴調試用的框架。

值得一提的是,SE-0271 的 「Future Directions」 一節中提到了對資源的安全訪問 —— 這意味着像 Image("avatar") 這樣的代碼以後會變成 Image(module.avatar)


個人公衆號 這裏有Swift及計算機編程的相關文章,以及優秀國外文章翻譯,歡迎關注~

Swift花園微信公衆號
相關文章
相關標籤/搜索