Swift 5.2 初探:有甚麼嶄新的功能等著你去探索?


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

隨著 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 {{EJS0}}
        }
    }
}
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.1.3

現在,在 Swift 5.2 (Xcode 11.4) 則顯示為:

Swift 5.2

總結

主要來說,Swift 5.2 改善了開發體驗,讓我們探索其豐富的功能。除了針對編譯器錯誤的新診斷引擎外,這裡還有許多嶄新的測試和除錯功能等著你去探索!

你可以看看這篇、或是這篇文章來了解更多。

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

作者簡介:Megha Aneja,Tokopedia 的 iOS App 開發者,歡迎以電郵與我聯絡。

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


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

blog comments powered by Disqus
Shares
Share This