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ă")
复制代码
一步一步来:
- 为教程声明标题和作者,因为教程实现了 BlogPost 。
-
检查
title
和
author
是否有效,如果测试成功,则从
createBlogPost(title:author :)
返回 Tutorial 。 -
使用
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
}
复制代码
以上代码的工作原理如下:
- 使用**@propertyWrapper 注释 SettingsWrapper**,使其成为属性包装器类型。
- 使用 wrappedValue 在设置中获取和设置键。
- 标记 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
}
复制代码
逐步浏览上述守则:
- 使用init(unsafeUninitializedCapacity:initializingWith:)创建具有特定初始容量的随机开关。
- 循环通过随机开关,并使用random()设置每个开关状态。
- 为随机开关设置初始化元素的数量。
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
复制代码
下面是这段代码的作用:
- 使用inferringMoves()确定差异中的移动,并循环遍历它们。
- 如果change是.insert(offset:element:associatedWith:),则在偏移量处向答案添加元素;如果associatedWith不是nil,则将插入视为移动。
- 如果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
复制代码
事情是这样的:
- 将文件标记为@dynamicMemberLookup,以便为自定义下标启用点语法。
- 创建一个静态下标,返回文件的默认或自定义路径。
- 使用动态成员查找定义前一个下标的类版本。
- 使用相应的语法调用这两个下标。
注:想了解更多关于斯威夫特下标?查看下标教程:自定义下标在斯威夫特。
动态成员查找键路径
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
复制代码
一步一步来:
- 声明x和y为Point。
- 使用@dynamicMemberLookup注释Circle,以启用其下标的点语法。
- 创建一个通用下标,它使用键路径从Circle访问center属性。
- 使用动态成员查找而不是键路径在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]
复制代码
事情是这样的:
- 申报仪器的品牌、年份及详细资料。
- 使用键路径从乐器的细节中获取类型和音高。
适用于弱特性和无主特性,具有相同的可拆卸一致性
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