专栏名称: CainLuo
iOS
目录
相关文章推荐
爱可可-爱生活  ·  【[924星]LegendApp/legen ... ·  昨天  
爱可可-爱生活  ·  【[254星]openai-realtime ... ·  昨天  
命里有票  ·  用DeepSeek和豆包分别计算了一个日期问 ... ·  3 天前  
命里有票  ·  用DeepSeek和豆包分别计算了一个日期问 ... ·  3 天前  
51好读  ›  专栏  ›  CainLuo

Swift 5.1有什么新功能?

CainLuo  · 掘金  ·  · 2019-08-06 15:24

正文

阅读 15

Swift 5.1有什么新功能?

Swift 5.1 终于发布了!本文将带您了解该语言在最新版本中必须提供的改进和更改。

注意: 当前版本为Swift5, iOS 13, Xcode 11

文章是由机翻过来的, 等有空再好好整理一下, 现在大家先凑合着看吧

好消息: Swift 5.1 现在可以在 Xcode 11 beta 版中使用了!这个版本带来了模块的稳定性,并改进了具有重要特性的语言。在本教程中,您将了解 Swift 5.1 的新特性。你需要 Xcode 11 beta 版才能与 Swift 5.1 兼容,所以在开始之前安装它吧。

入门

Swift 5.1 Swift 5 兼容。由于 ABI 稳定性,它还与 Swift 5 以及未来版本的 Swift 二进制兼容。

Swift 5.1 Swift 5 中引入的 ABI 稳定性之上增加了模块稳定性。虽然 ABI 稳定性在运行时负责应用程序兼容性,但模块稳定性使编译时的库兼容性成为可能。 这意味着您可以将第三方框架与任何编译器版本一起使用,而不是仅使用它构建的版本。

每个教程部分都包含 Swift Evolution 建议编号,例如**[SE-0001]**。 您可以通过单击每个提案的链接标记来浏览每个更改。

我建议您通过在操场上尝试新功能来学习本教程。 启动 Xcode 11 并转到 File ▸ New ▸ Playground 。 选择 iOS 作为平台,选择空白作为模板。 将其命名并将其保存在您想要的位置。 开始的时候了!

注:需要重温Swift 5的亮点吗?查看Swift 5教程: Swift 5 有什么新功能?

语言改进

此版本中有许多语言改进,包括不透明的结果类型,函数构建器,属性包装器等。

不透明的结果类型

您可以使用协议作为 Swift 5 中函数的返回类型。

打开新的 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 页面不透明教程,并粘贴在它:

// 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. 检查 title author 是否有效,如果测试成功,则从 createBlogPost(title:author :) 返回 Tutorial
  3. 使用 createBlogPost(title:author:) 创建 swift4Tutorial swift5Tutorial

您还可以重用 createBlogPost(title:author:) 的原型和逻辑来创建屏幕广播,因为屏幕广播也是隐藏在幕后的博客文章。

右键单击顶层 Playground 并选择 New playground Page 。重命名新的 Playground 页面不透明的屏幕截图,并粘贴到其中:

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:) 创建 swift4Screencast swift5Screencast

导航到源文件夹中的 BlogPost.swift ,并使 BlogPost 符合 Equatable

public protocol BlogPost: Equatable {
  var title: String { get }
  var author: String { get }
}
复制代码

此时,您将得到一个错误,即 BlogPost 只能用作通用约束。这是因为 Equatable 有一个名为 Self 的关联类型。具有关联类型的协议不是类型,即使它们看起来像类型。相反,它们有点像类型占位符,说“这可以是任何符合该协议的具体类型”。

Swift 5.1 允许您使用这些协议作为常规类型,使用不透明的结果类型 SE-0244

在不透明的教程页面中,向 createBlogPost 的返回类型添加一些,表示它返回 BlogPost 的具体实现。

func createBlogPost(title: String, author: String) -> some BlogPost {
复制代码

类似地,在不透明的屏幕显示页面中,使用some来告诉编译器 createBlogPost 返回某种类型的 BlogPost

func createBlogPost(title: String, author: String) -> some BlogPost {
复制代码

您可以从 createBlogPost: Tutorial Screencast 返回实现 BlogPost 的任何具体类型。

现在,您可以检查之前创建的教程和屏幕截图是否相同。在 Opaque Tutorials 的底部,粘贴以下代码来检查 swift4Tutorial swift5Tutorial 是否相同。

let sameTutorial = swift4Tutorial == swift5Tutorial
复制代码

在不透明的屏幕截图的底部,粘贴以下内容,检查 swift4Screencast swift5Screencast 是否相同。

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
复制代码

isSwift isLatestVersion 在设置中获取和设置给定键的值。 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. 标记 isSwift isLatestVersion 作为**@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. 为随机开关设置初始化元素的数量。

dif命令集合

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"]
复制代码

获取操作系统和答案之间的差异(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 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. 声明x和y为Point。
  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. 使用键路径从乐器的细节中获取类型和音高。

适用于弱特性和无主特性,具有相同的可拆卸一致性

Swift 5.1 自动合成具有弱和无标识存储特性的结构的可均衡和可耐洗一致性。

假设您有两个类:

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:), Key和Chord都符合Equatable和Hashable。

如果你在结构体中使用这些类, Swift 5.1 将能够合成耐洗一致性:

struct Tune: Hashable {
  unowned let key: Key






请到「今天看啥」查看全文