Swift 5.1 中引入的部分有用的新特性

Swift 5.1現在已經正式發布,盡管只是次要版本,它包含了大量的更改和改進。從基本的新功能,例如模塊穩定性(使SDK供應商可以交付預編譯的Swift框架)到所有SwiftUI以及其他功能的新語法功能。

除了具有標題的新功能外,Swift 5.1還包含許多較小的但仍然非常重要的新功能和改進。乍一看,這種變化似乎很小,甚至是不必要的,但可能會對我們編寫和構建Swift代碼的方式產生重大影響。
Swift 5.1 - 簡書

1、函數、閉包單表達式函數的隱式返回

  • 現在,在聲明僅包含單個表達式的函數和計算屬性時,可以省略return關鍵字,這使得在聲明更簡單便捷的API時非常友好:
//單行表達式
func increase(number: Int) -> Int {
    number + 1
}

//計算屬性
struct Message {
    var title: String
    var info: String
    let description: {title + ": " + info}
}

2、具有默認值的成員初始化器----自動合成結構體的構造參數

  • 原來的結構體屬性有默認值時,不會生成有可選屬性參數的構造函數,現在可以了
struct Message {
    var title: String
    var info: "body"
}

在swift 5.1 中,下方初始化方法均正確

var message = Message(title: "title")
var message = Message(title: "title", info: "info body")

3、Self 關鍵字

3.1、靜態成員的 Self
  • Swift 5.1之后,可以使用 Self替代類名來訪問靜態成員
class ListViewController: UITableViewController {
    static let cellReuseIdentifier = "list-cell-identifier"

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(
            ListTableViewCell.self,
            forCellReuseIdentifier: Self.cellReuseIdentifier
        )
    }
}
3.2、使用 Self 動態獲取引用類型
  • SwiftSelf關鍵字(或類型)使我們能夠在未知具體類型的上下文中動態引用實際上的類型,例如,通過在協議擴展中引用協議的實現類型:
extension Numeric {
    func incremented(by value: Self = 1) -> Self {
        return self + value
    }
}
  • 我們給Numeric協議擴展了一個自增的方法,但是我們現在不知道具體自增的類型,使用Self作為返回類型,則可以動態獲取對應的類型:
let num1 = 5.incremented()           //num1: Int
let num2 = 5.0.incremented()         //num2: Double
3.3 使用Self引用封閉類型
  • Self的范圍現已擴展到還包括具體類型(例如枚舉,結構體和類),使我們能夠將Self用作一種引用方法或屬性的封閉類型的別名,如下所示:
struct TextTransform {
    let closure: (String) -> String
}

extension TextTransform {
    static var capitalize: Self {
        return TextTransform { $0.capitalized }
    }

    static var removeLetters: Self {
        return TextTransform { $0.filter { !$0.isLetter } }
    }
}

我們現在可以在上方使用Self而不是完整的TextTransform類型名稱看,當然這純粹是語法糖——但它可以使我們的代碼更緊湊,尤其是在處理長類型名稱時。我們甚至還可以在方法或屬性中使用Self內聯,同時使用隱式返回,進一步使上述代碼更加緊湊:

extension TextTransform {
    static var capitalize: Self {
        Self { $0.capitalized }
    }

    static var removeLetters: Self {
        Self { $0.filter { !$0.isLetter } }
    }
}

給String擴展兩個方法:

extension String {
    func withTransform(_ textTransform: TextTransform) -> String {
        textTransform.closure(self)
    }
    
    mutating func withTransforms(_ textTransforms: [TextTransform]) -> String {
        textTransforms.forEach{ trans in
            self = self.withTransform(trans)
        }
        return self
    }
}

let singelUse = "i am a string"
    .withTransform(.capitalize)
    .withTransform(.removeLetters)

var str = "i am a string"
let groupUse = str.withTransforms([
    .capitalize,
    .removeLetters
])

4、屬性包裝類型(Property Wrapper Types)

在 iOS 開發中,經常要用到@IBOutlet@IBAction,在Swift中,越來越多@修飾的關鍵字出現,比如 @UIApplicationMain,特別是在 SwiftUI 中,會發現有很多類似這樣的關鍵字。

  • swift5.1中新增了一個 @propertyWrapper
  • 用它來修飾一個一個結構體,它修飾的結構體可以變成一個新的修飾符并作用在其他代碼上,來改變這些代碼的默認行為。
@propertyWrapper
struct Trimmed {
    private var value: String = ""
    var wrappedValue: String {
        get { value }
        set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
    }
    init(wrappedValue initialValue: String) {
        self.wrappedValue = initialValue
    }
}

struct Message {
    @Trimmed var title: String
    @Trimmed var info: String
}
//任何字符串無論是在初始化期間還是通過后面的屬性訪問都會自動刪除前后面的空格。
var msg = Message(title: "  Swift5.1 Property Wrappers  ", info: "  is a new and important  key words")
let title = msg.title // "Swift5.1 Property Wrappers"
let info = msg.info  // "is a new and important  key words"

5、有序集合的差異

  • 作為Swift 5.1的一部分引入的全新標準庫API,有序集合差異(ordered collection diffing)。 隨著CombineSwiftUI之類的工具越來越接近聲明式編程的世界,能夠計算兩種狀態之間的差異變得越來越重要。
  • 畢竟,聲明性UI開發就是關于不斷呈現狀態的新快照的,而且盡管SwiftUI和新的diffable數據源可能會完成大部分繁重的工作來實現這一點,但能夠計算出我們自己在兩種狀態之間的差異可能是非常有用。
  • 例如,假設我們正在構建一個DatabaseController,它將使我們可以使用一系列內存模型輕松地更新磁盤上的數據庫。為了能夠確定是應該插入還是刪除模型,我們現在可以簡單地調用新的差異API來計算舊數組與新數組之間的差異-然后迭代該差異中的更改以執行我們的數據庫操作:
class DatabaseController<Model: Hashable & Identifiable> {
    private let database: Database
    private(set) var models: [Model] = []
    
    ...

    func update(with newModels: [Model]) {
        let diff = newModels.difference(from: models)

        for change in diff {
            switch change {
            case .insert(_, let model, _):
                database.insert(model)
            case .remove(_, let model, _):
                database.delete(model)
            }
        }

        models = newModels
    }
}
  • 但是,上述實現并未考慮已移動的模型-因為默認情況下,移動將被視為單獨的插入和刪除。為了解決這個問題,我們在計算diff時也要調用inferringMoves方法,然后查看每個插入是否與移除關聯,如果這樣,則將其視為移動,如下所示:
func update(with newModels: [Model]) {
    let diff = newModels.difference(from: models).inferringMoves()
    
    for change in diff {
        switch change {
        case .insert(let index, let model, let association):
            // 如果 associated index 不為 nil, 則意味著這個inset操作實際上是move
            if association != nil {
                database.move(model, toIndex: index)
            } else {
                database.insert(model)
            }
        case .remove(_, let model, let association):
            // 我們將只處理 association 為 nil的情況,其他情況已經在上方處理
            if association == nil {
                database.delete(model)
            }
        }
    }

    models = newModels
}
  • 現在,將差異內置到標準庫(以及UIKit和AppKit中)的事實真是太了不起了——因為編寫高效,靈活且健壯的差異算法非常困難。

6、將協議拋出錯誤功能實現為非拋出

  • 在Swift中,可以使用非拋出函數滿足拋出錯誤函數協議的要求,這在某些情況下非常有用。例如,假設我們為解析器定義了一個協議,該協議使我們可以通過某種方式對字符串進行標記:
protocol TokenParser {
    func parseToken(from string: String) throws -> Token
}
  • 盡管上述協議的某些實現需要拋出,但不一定對所有符合條件的類型都適用。例如,下面的KeywordParser拋出,而TextParser沒有:
struct KeywordParser: TokenParser {
    func parseToken(from string: String) throws -> Token {
        ...
    }
}

struct TextParser: TokenParser {
    //這仍然能夠符合我們的協議,雖然它沒有 throws
    func parseToken(from string: String) -> Token {
        ...
    }
}
  • 由于我們協議功能的原始聲明被標記為throw,因此在確切的確切類型未知時,我們總是需要使用try來調用它——不管基礎實現是否實際拋出:
let parsers: [TokenParser] = ...

for parser in parsers {
    let token = try parser.parseToken(from: string)
}
  • 但是,當直接處理非拋類型時,我們現在可以省略try關鍵字——即使原始協議要求已標記為throws:
let parser = TextParser()
let text = parser.parseToken(from: string)
  • 這是一個很小的功能,但事實是,我們可以使用非拋出函數來實現拋出函數的要求,這使我們在遵守包含此類函數的協議時具有更高的靈活性。

7、字符串插值新協議ExpressibleByStringInterpolation——使類型可以使用字符串插值

  • 為諸如字符串和整數之類的原始值創建包裝器類型,是使我們的代碼更具類型安全性和自記錄性的好方法,并且還為我們提供了專用的類型,可以在這些類型上實現特定于域的便捷性API。
  • 例如,這種類型代表文件系統路徑,可用于執行諸如加載文件內容的操作:
struct Path {
    var string: String
}

func loadFile(at path: Path) throws -> File {
    ...
}
  • 但是,雖然擁有專用的Path類型確實給我們帶來了很多好處,但它也可能使我們的API使用起來更加麻煩。例如,任何時候我們想要使用字符串文字來指定路徑時,我們現在都必須先將其包裝起來:
try loadFile(at: Path(string: "~/documents/article.md"))
  • 為了解決這個問題,我們可以使Path符合ExpressibleByStringLiteral,這使我們能夠直接將字符串文字傳遞給任何接受Path的API:
extension Path: ExpressibleByStringLiteral {
    init(stringLiteral value: String) {
        self.string = value
    }
}
try loadFile(at: "~/documents/article.md")
  • 這樣已經非常好了,但是如果我們在字符串中使用任何形式的插值,則上述方法將無法正常工作,例如:
try loadFile(at: "/users/\(username)/file.txt")

現在,swift5.1引入了新協議ExpressibleByStringInterpolation,只要使Path遵循這個協議,則上面的代碼就可以正常運行了,增加如下代碼:

extension Path: ExpressibleByStringInterpolation {}

8、返回值類型抽象化 —— some關鍵字

這里的some其實就是和一個稱為opaque(不透明)類型有關,在返回類型前面加上一些關鍵字表示返回類型是不透明的,不透明類型通常被稱為反向泛型類型。
比如我定一個一個Animal協議,具有關聯類型LikeType

protocol Animal {
    associatedtype LikeType
    var name: String { get }
    var like: LikeType { get }
}

struct Dog: Animal {
    var name: String
    var like: Bark
}

struct Pig: Animal {
    var name: String
    var like: Sleep
}

此時我們實現一個方法去識別動物:

func identityAnimal() -> Animal

這在swift中是無法編譯通過的,因為swift不能把帶有關聯類型的協議類型作為返回類型,這個時候就輪到some上場了:

func identityAnimal() -> some Animal {
    return Pig(name: "pink pig", like: Sleep("every day"))
}

var animal: some Animal = identityAnimal()

參考文檔:
5 small but significant improvements in Swift 5.1
Implementing throwing protocol functions as non-throwing
Making types expressible by string interpolation

賞我一個贊吧~~~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯系作者。

推薦閱讀更多精彩內容