Swift 5.1有什麼新功能?

Swift 5.1終於發佈了!本文將帶您瞭解該語言在最新版本中必須提供的改進和更改。git

注意: 當前版本爲Swift 5, iOS 13, Xcode 11, 如轉載本文章, 請聯繫做者, 並給出文章的源地址github

好消息:Swift 5.1如今能夠在Xcode 11 beta版中使用了!這個版本帶來了模塊的穩定性,並改進了具備重要特性的語言。在本教程中,您將瞭解Swift 5.1的新特性。你須要Xcode 11 beta版才能與Swift 5.1兼容,因此在開始以前安裝它吧。編程

入門

Swift 5.1Swift 5兼容。因爲ABI穩定性,它還與Swift 5以及將來版本的Swift二進制兼容。swift

Swift 5.1Swift 5中引入的ABI穩定性之上增長了模塊穩定性。雖然ABI穩定性在運行時負責應用程序兼容性,但模塊穩定性使編譯時的庫兼容性成爲可能。 這意味着您能夠將第三方框架與任何編譯器版本一塊兒使用,而不是僅使用它構建的版本。api

每一個教程部分都包含Swift Evolution建議編號,例如**[SE-0001]**。 您能夠經過單擊每一個提案的連接標記來瀏覽每一個更改。數組

我建議您經過在操場上嘗試新功能來學習本教程。 啓動Xcode 11並轉到File ▸ New ▸ Playground。 選擇iOS做爲平臺,選擇空白做爲模板。 將其命名並將其保存在您想要的位置。 開始的時候了!閉包

注:須要重溫Swift 5的亮點嗎?查看Swift 5教程:Swift 5有什麼新功能?app

語言改進

此版本中有許多語言改進,包括不透明的結果類型,函數構建器,屬性包裝器等。框架

Opaque Result Types

您可使用協議做爲Swift 5中函數的返回類型。less

打開新的Playground後,經過導航到View ▸ Navigators ▸ Show Project Navigator打開項目導航器。 右鍵單擊Sources文件夾,選擇New File並將文件命名爲BlogPost。 使用名爲BlogPost的新協議的定義替換新文件的內容。

public protocol BlogPost {
  var title: String { get }
  var author: String { get }
}
複製代碼

右鍵單擊頂層Playground並選擇New playground Page。從新命名新的Playground頁面Opaque教程,並粘貼在它:

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

// 2
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)
}

// 3
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. 檢查titleauthor是否有效,若是測試成功,則從createBlogPost(title:author :)返回Tutorial
  3. 使用createBlogPost(title:author:)建立swift4Tutorialswift5Tutorial

您還能夠重用createBlogPost(title:author:)的原型和邏輯來建立屏幕廣播,由於屏幕廣播也是隱藏在幕後的博客文章。

右鍵單擊頂層Playground並選擇New playground Page。重命名新的Playground頁面Opaque的屏幕截圖,並粘貼到其中:

struct Screencast: 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 Screencast(title: title, author: author)
}

let swift4Screencast = createBlogPost(title: "What's new in Swift 4.2?", 
                                      author: "Josh Steele")           
let swift5Screencast = createBlogPost(title: "What's new in Swift 5?", 
                                      author: "Josh Steele")
複製代碼

Screencast實現了BlogPost,所以您能夠從createBlogPost(title:author:)返回Screencast,並使用createBlogPost(title:author:)建立swift4Screencastswift5Screencast

導航到源文件夾中的BlogPost.swift,並使BlogPost符合Equatable

public protocol BlogPost: Equatable {
  var title: String { get }
  var author: String { get }
}
複製代碼

此時,您將獲得一個錯誤,即BlogPost只能用做通用約束。這是由於Equatable有一個名爲Self的關聯類型。具備關聯類型的協議不是類型,即便它們看起來像類型。相反,它們有點像類型佔位符,說「這能夠是任何符合該協議的具體類型」。

Swift 5.1容許您使用這些協議做爲常規類型,使用不透明的結果類型SE-0244

Opaque的教程頁面中,向createBlogPost的返回類型添加一些,表示它返回BlogPost的具體實現。

func createBlogPost(title: String, author: String) -> some BlogPost {
複製代碼

相似地,在Opaque的屏幕顯示頁面中,使用some來告訴編譯器createBlogPost返回某種類型的BlogPost

func createBlogPost(title: String, author: String) -> some BlogPost {
複製代碼

您能夠從createBlogPost: TutorialScreencast返回實現BlogPost的任何具體類型。

如今,您能夠檢查以前建立的教程和屏幕截圖是否相同。在Opaque Tutorials的底部,粘貼如下代碼來檢查swift4Tutorialswift5Tutorial是否相同。

let sameTutorial = swift4Tutorial == swift5Tutorial
複製代碼

在不透明的屏幕截圖的底部,粘貼如下內容,檢查swift4Screencastswift5Screencast是否相同。

let sameScreencast = swift4Screencast == swift5Screencast
複製代碼

單表達式函數隱式返回

Swift 5的單表達式函數中使用return:

extension Sequence where Element == Int {
  func addEvenNumbers() -> Int {
    return reduce(0) { $1.isMultiple(of: 2) ? $0 + $1 : $0 }
  }

  func addOddNumbers() -> Int {
    return reduce(0) { $1.isMultiple(of: 2) ? $0 : $0 + $1 }
  }
}

let numbers = [10, 5, 2, 7, 4]
let evenSum = numbers.addEvenNumbers()
let oddSum = numbers.addOddNumbers()
複製代碼

addEvenNumbers()addOddNumbers()中使用reduce(_:_:)來肯定偶數和奇數的和。

Swift 5.1下降了單表達式函數的返回值,所以在本例中它們的行爲相似於單行閉包SE-0255:

extension Sequence where Element == Int {
  func addEvenNumbers() -> Int {
    reduce(0) { $1.isMultiple(of: 2) ? $0 + $1 : $0 }
  }

  func addOddNumbers() -> Int {
    reduce(0) { $1.isMultiple(of: 2) ? $0 : $0 + $1 }
  }
}
複製代碼

這一次代碼更簡潔,更容易理解。

注:想了解更多關於reduce(_:_:)如何在Swift中工做?查看函數式編程教程: Swift中的函數式編程介紹

函數構造器

Swift 5.1使用函數構建器實現構建器模式SE-XXXX:

@_functionBuilder
struct SumBuilder {
  static func buildBlock(_ numbers: Int...) -> Int {
    return numbers.reduce(0, +)
  }
}
複製代碼

使用**@_functionBuilder註釋SumBuilder**,使其成爲函數生成器類型。函數構造器是一種特殊類型的函數,其中每一個表達式(文字、變量名、函數調用、if語句等)都是單獨處理的,並用於生成單個值。例如,您能夠編寫一個函數,其中每一個表達式都將該表達式的結果添加到數組中,從而使您本身的數組成爲文字類型。

注意:在Xcode beta中,函數構建器的註釋是**@_functionBuilder**,由於這個建議尚未獲得批准。一旦得到批准,預期註釋將成爲**@functionBuilder**。

經過實現具備特定名稱和類型簽名的不一樣靜態函數,能夠建立函數構建器。buildBlock(_: T...)是唯一必需的。還有一些函數能夠處理if語句、選項和其餘能夠做爲表達式處理的結構。

使用函數生成器時,要用類名註釋函數或閉包:

func getSum(@SumBuilder builder: () -> Int) -> Int {
  builder()
}

let gcd = getSum {
  8
  12
  5
}
複製代碼

傳遞給getSum的閉包計算每一個表達式(在本例中是三個數字),並將這些表達式的結果列表傳遞給構建器。函數構建器以及隱式返回是SwiftUI乾淨語法的構建塊。它們還容許您建立本身的特定於域的語言。

屬性包裝

當你在Swift 5中處理計算屬性時,你要處理不少樣板代碼:

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
複製代碼

isSwiftisLatestVersion在設置中獲取和設置給定鍵的值。Swift 5.1經過定義屬性包裝器SE-0258去除重複代碼:

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

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

// 3
struct Settings {
  @SettingsWrapper(key: "swift", defaultValue: false) var isSwift: Bool
  @SettingsWrapper(key: "latestVersion", defaultValue: false) 
    var isLatestVersion: Bool
}
複製代碼

以上代碼的工做原理以下:

  1. 使用**@propertyWrapper註釋SettingsWrapper**,使其成爲屬性包裝器類型。
  2. 使用wrappedValue在設置中獲取和設置鍵。
  3. 標記isSwiftisLatestVersion做爲**@SettingsWrapper**來使用相應的包裝器實現它們。

圖片

合成結構中初始化函數的默認值

默認狀況下,Swift 5不會爲結構中的屬性設置初始值,因此您能夠爲它們定義自定義初始化器:

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")
複製代碼

在這裏,若是做者經過了測試並在網站上加入了教程團隊,則將tutorialCount設置爲0

Swift 5.1容許直接設置結構屬性的默認值,所以再也不須要自定義初始化器SE-0242:

struct Author {
  let name: String
  var tutorialCount = 0
}
複製代碼

這一次代碼更乾淨、更簡單。

靜態成員的Self

Swift 5中,你不能使用Self來引用數據類型的靜態成員,因此你必須使用類型名:

struct Editor {
  static func reviewGuidelines() {
    print("Review editing guidelines.")
  }

  func edit() {
    Editor.reviewGuidelines()
    print("Ready for editing!")
  }
}

let editor = Editor()
editor.edit()
複製代碼

網站上的編輯在編輯教程以前會檢查編輯指南,由於它們老是在變化。

你能夠用Swift 5.1 SE-0068中的Self重寫整個代碼:

struct Editor {
  static func reviewGuidelines() {
    print("Review editing guidelines.")
  }

  func edit() {
    Self.reviewGuidelines()
    print("Ready for editing!")
  }
}
複製代碼

此次使用Self調用reviewGuidelines()

建立未初始化數組

您能夠在Swift 5.1 SE-0245中建立未初始化的數組:

// 1
let randomSwitches = Array<String>(unsafeUninitializedCapacity: 5) {
  buffer, count in
  // 2
  for i in 0..<5 {
    buffer[i] = Bool.random() ? "on" : "off"
  }
  // 3
  count = 5
}
複製代碼

逐步瀏覽上述守則:

  1. 使用init(unsafeUninitializedCapacity:initializingWith:)建立具備特定初始容量的隨機開關。
  2. 循環經過隨機開關,並使用random()設置每一個開關狀態。
  3. 爲隨機開關設置初始化元素的數量。

Diffing命令集合

Swift 5.1容許您肯定有序集合之間的差別SE-0240

假設有兩個數組:

let operatingSystems = ["Yosemite",
                        "El Capitan",
                        "Sierra",
                        "High Sierra",
                        "Mojave",
                        "Catalina"]
var answers = ["Mojave",
               "High Sierra",
               "Sierra",
               "El Capitan",
               "Yosemite",
               "Mavericks"]
複製代碼

operatingSystems包含了全部的macOS版本,從最老的版本到最新的版本。答案以相反的順序列出它們,同時添加和刪除其中一些。

區分集合要求您使用#if Swift(>=)檢查最新的Swift版本,由於全部區分方法都標記爲**@available for Swift 5.1**:

#if swift(>=5.1)
  let differences = operatingSystems.difference(from: answers)
  let sameAnswers = answers.applying(differences) ?? []
  // ["Yosemite", "El Capitan", "Sierra", "High Sierra", "Mojave", "Catalina"]
複製代碼

獲取操做系統和答案之間的difference(from:),並使用apply(_:)將它們應用於答案。

或者,你也能夠手動操做:

// 1
  for change in differences.inferringMoves() {
    switch change {
      // 2
      case .insert(let offset, let element, let associatedWith):
        answers.insert(element, at: offset)
        guard let associatedWith = associatedWith else {
          print("\(element) inserted at position \(offset + 1).")
          break
        }
        print("""
              \(element) moved from position \(associatedWith + 1) to position 
              \(offset + 1).
              """)
      // 3
      case .remove(let offset, let element, let associatedWith):
        answers.remove(at: offset)
        guard let associatedWith = associatedWith else {
          print("\(element) removed from position \(offset + 1).")
          break
        }
        print("""
              \(element) removed from position \(offset + 1) because it should be 
                at position \(associatedWith + 1).
              """)
    }
  }
#endif
複製代碼

下面是這段代碼的做用:

  1. 使用inferringMoves()肯定差別中的移動,並循環遍歷它們。
  2. 若是change.insert(offset:element:associatedWith:),則在偏移量處向答案添加元素;若是associatedWith不是nil,則將插入視爲移動。
  3. 若是change.remove(offset:element:associatedWith:),則從答案的偏移處刪除元素,若是associatedWith不是nil,則認爲刪除是一個移動。

圖片

靜態和類下標

Swift 5.1容許您在類中聲明靜態和類下標SE-0254:

// 1
@dynamicMemberLookup
class File {
  let name: String

  init(name: String) {
    self.name = name
  }

  // 2
  static subscript(key: String) -> String {
    switch key {
      case "path":
        return "custom path"
      default:
        return "default path"
    }
  }

  // 3
  class subscript(dynamicMember key: String) -> String {
    switch key {
      case "path":
        return "custom path"
      default:
        return "default path"
    }
  }
}

// 4
File["path"]
File["PATH"]
File.path
File.PATH
複製代碼

事情是這樣的:

  1. 將文件標記爲**@dynamicMemberLookup**,以便爲自定義下標啓用點語法。
  2. 建立一個靜態下標,返回文件的默認或自定義路徑。
  3. 使用動態成員查找定義前一個下標的類版本。
  4. 使用相應的語法調用這兩個下標。

注:想了解更多關於斯威夫特下標?查看下標教程: 自定義Swift下標

動態查找成員變量的路徑

Swift 5.1實現鍵路徑的動態成員查找SE-0252:

// 1
struct Point {
  let x, y: Int
}

// 2
@dynamicMemberLookup
struct Circle<T> {
  let center: T
  let radius: Int

  // 3
  subscript<U>(dynamicMember keyPath: KeyPath<T, U>) -> U {
    center[keyPath: keyPath]
  }
}

// 4
let center = Point(x: 1, y: 2)
let circle = Circle(center: center, radius: 1)
circle.x
circle.y
複製代碼

一步一步來:

  1. 聲明xyPoint
  2. 使用**@dynamicMemberLookup註釋Circle**,以啓用其下標的點語法。
  3. 建立一個通用下標,它使用鍵路徑從Circle訪問center屬性。
  4. 使用動態成員查找而不是鍵路徑在circle上調用中心屬性。

注:須要更多關於如何在斯威夫特動態成員查找工做的細節?查看動態特性教程: Swift中的動態特性

Keypaths元組

你能夠在Swift 5.1中使用元組的關鍵路徑:

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

// 2
let instrument = Instrument(brand: "Roland",
                            year: 2019,
                            details: (type: "acoustic", pitch: "C"))
let type = instrument[keyPath: \Instrument.details.type]
let pitch = instrument[keyPath: \Instrument.details.pitch]
複製代碼

事情是這樣的:

  1. 申報儀器的品牌、年份及詳細資料。
  2. 使用鍵路徑從樂器的細節中獲取類型和音高。

weak和unknown屬性的Equatable和Hashable一致性

Swift 5.1自動合成具備弱和無標識存儲特性的結構的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)
  }
}
複製代碼

經過實現==(lhs:rhs:)hash(into:)KeyChord都符合EquatableHashable

若是你在結構體中使用這些類,Swift 5.1將可以合成Hashable:

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

let key = Key(note: "C")
let chord = Chord(note: "C")
let tune = Tune(key: key, chord: chord)
let chordlessTune = Tune(key: key, chord: nil)
let sameTune = tune == chordlessTune
let tuneSet: Set = [tune, chordlessTune]
let tuneDictionary = [tune: [tune.key.note, tune.chord?.note], 
                      chordlessTune: [chordlessTune.key.note, 
                      chordlessTune.chord?.note]]
複製代碼

TuneEquatableHashable,由於valuechordEquatableHashable

由於它是Hashable,你能夠將tunechordlessTune進行比較,將它們添加到tuneSet並將它們用做tuneDictionary的鍵。

可選的Enumeration Case

Swift 5.1爲可選枚舉狀況生成警告:

// 1
enum TutorialStyle {
  case cookbook, stepByStep, none
}

// 2
let style: TutorialStyle? = .none
複製代碼

這是如何工做的:

  1. TutorialStyle定義不一樣的樣式。
  2. Swift會發出警告,由於編譯器不清楚.none在這種狀況下的含義是什麼:Optional.noneTutorialStyle.none

匹配非可選項的可選枚舉

您可使用可選模式將非選項與Swift 5中的可選枚舉進行匹配:

// 1
enum TutorialStatus {
  case written, edited, published
}

// 2
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
}
複製代碼

上面的代碼執行如下操做:

  1. 聲明TutorialStatus的全部可能狀態。
  2. 使用可選模式打開狀態,由於您將其定義爲可選模式。

在這種狀況下,Swift 5.1刪除了可選的模式匹配:

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

這段代碼更清晰,更容易理解。

注意:想要了解有關Swift中模式匹配的更多信息? 查看模式匹配教程:Swift中的模式匹配

字符串的新功能

Swift 5.1爲字符串添加了一些急需的功能SE-0248

UTF8.width("S")
UTF8.isASCII(83)
複製代碼

在這裏,您肯定Unicode標量值的UTF-8編碼寬度,並檢查給定的代碼單元是否表示ASCII標量。 查看您可使用的其餘API的提案。

連續的字符串

Swift 5.1對連續字符串實現重要更改SE-0247

var string = "**Swift 5.1**"
if !string.isContiguousUTF8 {
  string.makeContiguousUTF8()
}
複製代碼

您檢查UTF-8編碼的字符串是否與isContiguousUTF8連續,並使用makeContiguousUTF8()來實現,若是不是。 看一下提案,看看你能夠用連續的字符串作些什麼。

其餘的一些改進

您應該瞭解Swift 5.1中的一些其餘功能:

轉換元組類型

Swift 5.1改進了元組類型的轉換:

let temperatures: (Int, Int) = (25, 30)
let convertedTemperatures: (Int?, Any) = temperatures
複製代碼

您能夠爲convertedTemperatures分配溫度,由於在這種狀況下您能夠將(Int, Int)轉換爲(Int?, Any)

具備重複標籤的元組

您能夠在Swift 5中聲明帶有重複標籤的元組:

let point = (coordinate: 1, coordinate: 2)
point.coordinate
複製代碼

在這種狀況下,不清楚座標是否從點返回第一個或第二個元素,所以Swift 5.1刪除了元組的重複標籤。

使用任何參數重載函數

Swift 5更喜歡任何參數而不是泛型參數,只有一個參數的函數重載:

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

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

showInfo("Swift 5")
複製代碼

在這種狀況下,showInfo()返回「Any value」。 Swift 5.1以相反的方式工做:

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

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

showInfo("**Swift 5.1**")
複製代碼

showInfo()此次返回「Generic value」。

爲自動關閉參數鍵入別名

你不能在Swift 5中爲**@autoclosure**參數聲明類型別名:

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

apply(closure :)在這種狀況下使用autoclosure聲明閉包。 您能夠在Swift 5.1中的apply(closure :)原型中使用類型別名:

struct Closure<T> {
  typealias ClosureType = () -> T

  func apply(closure: @autoclosure ClosureType) {
    closure()
  }
}
複製代碼

apply(closure :)此次使用ClosureType進行閉包。

從Objective-C方法返回self

若是你的類包含一個在Swift 5中返回Self的**@objc方法,你必須從NSObject**繼承:

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

由於Clone擴展了NSObject,因此clone()返回Self。 在Swift 5.1中再也不是這種狀況:

class Clone {
  @objc func clone() -> Self {
    self
  }
}
複製代碼

克隆此次沒必要繼承任何東西。

穩定的ABI圖書館

您能夠在Swift 5.1中使用**-enable-library-evolution來更改庫類型而不會破壞其ABI**。 標記爲**@frozen**的結構和枚舉不能添加,刪除或從新排序存儲的屬性和案例SE-0260

而後去哪兒?

您可使用本教程頂部或底部的「下載材料」連接下載最終的Playground

Swift 5.1Swift 5中已經引入的功能添加了許多不錯的功能。它還爲語言帶來了模塊穩定性,並實現了WWDC中引入的新框架(如SwiftUICombine)所使用的複雜範例。

您能夠在官方Swift CHANGELOGSwift標準庫差別上閱讀有關此Swift版本更改的更多信息。

您還能夠查看Swift Evolution提案,瞭解下一版Swift的內容。 在這裏,您能夠爲當前審覈的提案提供反饋,甚至能夠自行提交提案!

項目示例: 工程示例

相關文章
相關標籤/搜索