Swift 5.1 新特性

在 7 月 29 日的發布的 Xcode 11 beta 5 中,包含了 Swift 5.1。如果想要體驗這些新特性,需要至少安裝好這個版本的 Xcode。本文內容主要參考 Raywenderlich這篇文章 編寫,如果想要查看原文,請點擊鏈接查看。

Swift 5.1 在 5.0 引入的 ABI 穩定性基礎上增加了模塊穩定性。雖然 ABI 穩定性在運行時考慮到應用程序的兼容性,但模塊穩定性在編譯時支持庫的兼容性。這意味著你可以在任何編譯器版本中使用第三方框架,而不只是構建它的那個版本。

下面我們一起看一下有哪些改進。

模糊的結果類型 (Opaque Result Types)

在開發的時候,有時候可能會使用 protocol 作為返回值類型。下面來看一個例子:

protocol BlogPost {
    var title: String { get }
    var author: String { get }
}

struct Tutorial: BlogPost {
    let title: String
    let author: String
}

func createBlogPost(title: String, author: String) -> BlogPost {
    guard !title.isEmpty && !author.isEmpty else {
        fatalError("No title and/or author assigned!")
    }
    return Tutorial(title: title, author: author)
}

let swift4Tutorial = createBlogPost(title: "What's new in Swift 4.2?",
                                    author: "Cosmin Pup?z?")
let swift5Tutorial = createBlogPost(title: "What's new in Swift 5?",
                                    author: "Cosmin Pup?z?")

上面代碼:1)首先定義 BlogPost 協議;2)定義 Tutorial 并實現 BlogPost 協議;3)定義 createBlogPost() 方法用于創建 BlogPost;4)用 createBlogPost() 創建 swift4Tutorialswift5Tutorial 兩個實例。

下面我們想比較 swift4Tutorialswift5Tutorial 是否相等:

// 錯誤:Binary operator '==' cannot be applied to two 'BlogPost' operands
let isSameTutorial = (swift4Tutorial == swift5Tutorial)

因為 BlogPost 還沒有實現 Equatable,所以會出錯。下面讓 BlogPost 繼承自 Equatable

protocol BlogPost: Equatable {
    var title: String { get }
    var author: String { get }
}

這時另一個錯誤出現在 createBlogPost() 方法:

// Protocol 'BlogPost' can only be used as a generic constraint because it has Self or associated type requirements
func createBlogPost(title: String, author: String) -> BlogPost {
    guard !title.isEmpty && !author.isEmpty else {
        fatalError("No title and/or author assigned!")
    }
    return Tutorial(title: title, author: author)
}

這個錯誤的意思是 BlogPost 只能用來作為泛型約束,因為它有 Self 或者有關聯類型要求。查看 Equatable的定義,確實是有 Self

public protocol Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool
}

具有關聯類型的協議不是類型,即使他們看起來是類型。而它們更像是類型占位符,這個類型可以是任何實現了我這個協議的類型。

在 Swift 5.1 中,我們就可以在返回值類型前面加上 some 來解決這個問題。把 createBlogPost() 方法改為:

func createBlogPost(title: String, author: String) -> some BlogPost {
    guard !title.isEmpty && !author.isEmpty else {
        fatalError("No title and/or author assigned!")
    }
    return Tutorial(title: title, author: author)
}

some 的作用就是告訴編譯器我這個方法的返回值可以是實現了 BlogPost 的任何類型。

修改完成之后,我們直接用 == 比較 swift4Tutorialswift5Tutorial 就不會報錯了。

在 SwiftUI 中,就是使用了 some

struct ContentView: View {
    var body: some View {
        Text("Hello World")
    }
}

隱式返回

在 Swift 5.1 中,如果方法體只有一行語句,則可以省略 return

func myName() -> String {
    "Lebron"
}

屬性包裝器

在 Swift 5.1 之前,使用計算屬性時,可能出現類似下面的代碼:

var settings = ["swift": true, "latestVersion": true]

struct Settings {
    var isSwift: Bool {
        get {
            return settings["swift"] ?? false
        }
        set {
            settings["swift"] = newValue
        }
    }
    
    var isLatestVersion: Bool {
        get {
            return settings["latestVersion"] ?? false
        }
        set {
            settings["latestVersion"] = newValue
        }
    }
}

var newSettings = Settings()
newSettings.isSwift
newSettings.isLatestVersion
newSettings.isSwift = false
newSettings.isLatestVersion = false

上面的代碼中,如果有更多的計算屬性,那么就要寫跟多的重復代碼。為了解決這個問題,Swift 5.1 引入了屬性包裝器,可以把上面的代碼簡寫為:

var settings = ["swift": true, "latestVersion": true]

@propertyWrapper
struct SettingsWrapper {
  let key: String
  let defaultValue: Bool

  var wrappedValue: Bool {
    get {
      settings[key] ?? defaultValue
    }
    set {
      settings[key] = newValue
    }
  }
}

struct Settings {
  @SettingsWrapper(key: "swift", defaultValue: false)         var isSwift: Bool
  @SettingsWrapper(key: "latestVersion", defaultValue: false) var isLatestVersion: Bool
}
  • @propertyWrapperSettingsWrapper 標記為屬性包裝器。作為一個屬性包裝器,必須有一個名為wrappedValue 的屬性。
  • 使用 @SettingsWrapper 標記 Settings中對應的屬性。

在 struct 中定義屬性的默認值

在 Swift 5.1 前,如果想要給 struct 的屬性定義默認值,必須這么寫:

struct Author {
    let name: String
    var tutorialCount: Int
    
    init(name: String, tutorialCount: Int = 0) {
        self.name = name
        self.tutorialCount = tutorialCount
    }
}

let author = Author(name: "George")

而在 Swift 5.1 以后,可以直接像 class 那樣給屬性定義默認值:

struct Author {
    let name: String
    var tutorialCount = 0
}

使用 Self 調用靜態成員

在 Swift 5.1 以前,需要使用 類名.靜態成員來調用靜態成員:

struct Editor {
    static func reviewGuidelines() {
        print("Review editing guidelines.")
    }
    
    func edit() {
        Editor.reviewGuidelines()
        print("Ready for editing!")
    }
}

而在 Swift 5.1 中,可以直接用 Self.靜態成員

struct Editor {
    static func reviewGuidelines() {
        print("Review editing guidelines.")
    }
    
    func edit() {
        Self.reviewGuidelines()
        print("Ready for editing!")
    }
}

創建未初始化的數組

Swift 5.1 給 Array 添加了一個新的初始化方法:init(unsafeUninitializedCapacity:initializingWith:)

let randomSwitches = Array<String>(unsafeUninitializedCapacity: 5) { buffer, count in
    for i in 0..<5 {
        buffer[i] = Bool.random() ? "on" : "off"
    }
    // 必須給 `count` 賦值,否則 `randomSwitches` 會變成空數組
    count = 5
}

staticclass 下標

在 Swift 5.1 中可以定義 staticclass 下標:

@dynamicMemberLookup
class File {
    let name: String
    
    init(name: String) {
        self.name = name
    }
    
    // 定義 static 下標
    static subscript(key: String) -> String {
        switch key {
        case "path":
            return "custom path"
        default:
            return "default path"
        }
    }
    
    // 使用 Dynamic Member Lookup 重寫上面的下標
    class subscript(dynamicMember key: String) -> String {
        switch key {
        case "path":
            return "custom path"
        default:
            return "default path"
        }
    }
}

File["path"] // "custom path"
File["PATH"] // "default path"
File.path    // "custom path"
File.PATH    // "default path"

@dynamicMemberLookup 標記 File 是為了可以使用點語法來訪問自定義的下標。

Keypath 支持動態成員查找

struct Point {
    let x, y: Int
}

@dynamicMemberLookup
struct Circle<T> {
    let center: T
    let radius: Int
    
    // 定義泛型下標,可以用 keypath 來訪問 `center` 的屬性
    subscript<U>(dynamicMember keyPath: KeyPath<T, U>) -> U {
        center[keyPath: keyPath]
    }
}

let center = Point(x: 1, y: 2)
let circle = Circle(center: center, radius: 1)
circle.x // 1
circle.y // 2

Tuple 支持 Keypath

struct Instrument {
    let brand: String
    let year: Int
    let details: (type: String, pitch: String)
}

let instrument = Instrument(
    brand: "Roland",
    year: 2019,
    details: (type: "acoustic", pitch: "C")
)
// 使用 keypath 訪問 tuple
let type = instrument[keyPath: \Instrument.details.type]
let pitch = instrument[keyPath: \Instrument.details.pitch]

weakunowned 屬性自動實現 EquatableHashable

class Key {
    let note: String
    
    init(note: String) {
        self.note = note
    }
}

extension Key: Hashable {
    static func == (lhs: Key, rhs: Key) -> Bool {
        lhs.note == rhs.note
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(note)
    }
}

class Chord {
    let note: String
    
    init(note: String) {
        self.note = note
    }
}

extension Chord: Hashable {
    static func == (lhs: Chord, rhs: Chord) -> Bool {
        lhs.note == rhs.note
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(note)
    }
}

struct Tune: Hashable {
    unowned let key: Key
    weak var chord: Chord?
}

在 Swift 5.1 以前,Tune 的定義里面會報錯:沒有實現 EquatableHashable;在 Swift 5.1 則已經自動實現。

不明確的枚舉 case

如果有不明確的枚舉 case,在 Swift 5.1 中會產生警告??。

enum TutorialStyle {
  case cookbook, stepByStep, none
}

// 會產生警告
let style: TutorialStyle? = .none

因為 styleOptional 類型,編譯器不知道 .noneOptional.none 還是 TutorialStyle.none ,所有要寫具體一點,例如:let style: TutorialStyle? = TutorialStyle.none

匹配可選類型的枚舉

在 Swift 5.1 以前,switch 語句中匹配可選類型的枚舉時,case 后面需要加問號:

enum TutorialStatus {
    case written, edited, published
}

let status: TutorialStatus? = .published

switch status {
    case .written?:
        print("Ready for editing!")
    case .edited?:
        print("Ready to publish!")
    case .published?:
        print("Live!")
    case .none:
        break
}

而在 Swift 5.1 中,可以把問號去掉:

switch status {
    case .written:
        print("Ready for editing!")
    case .edited:
        print("Ready to publish!")
    case .published:
        print("Live!")
    case .none:
        break
}

Tuple 類型的轉換

let temperatures: (Int, Int) = (25, 30)
let convertedTemperatures: (Int?, Any) = temperatures

在 Swift 5.1 以前,(Int, Int) 是不能轉換成 (Int?, Any) 的,而在 Swift 5.1 可以。

Any 和泛型參數的方法重載

func showInfo(_: Any) -> String {
  return "Any value"
}

func showInfo<T>(_: T) -> String {
  return "Generic value"
}

showInfo("Swift")

在 Swift 5.1 以前,showInfo("Swift") 返回的是 Any value;而在 Swift 5.1 中,返回的是 Generic value。也就是說在 Swift 5.1 以前,Any 參數類型的方法優先;而在 Swift 5.1 中,泛型參數的方法優先。

autoclosure 參數可以定義別名

在 Swift 5.1 中,@autoclosure 標記的 closure 參數可以使用別名:

struct Closure<T> {
    typealias ClosureType = () -> T
    
    func apply(closure:  @autoclosure ClosureType) {
        closure()
    }
}

在 Swift 5.1 以前,只能這樣寫:

struct Closure<T> {
    func apply(closure: @autoclosure () -> T) {
        closure()
    }
}

在 Objective-C 方法中返回 self

在 Swift 5.1 以前,被 @objc 標記的方法中返回 self,必須繼承自 NSObject

class Clone: NSObject {
    @objc func clone() -> Self {
        return self
    }
}

在 Swift 5.1 中,則無需集成 NSObject

class Clone {
  @objc func clone() -> Self {
    self
  }
}

想及時看到我的新文章的,可以關注我。同時也歡迎加入我管理的Swift開發群:536353151

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

推薦閱讀更多精彩內容