Getting Started
Swift 5.1 与 Swift 5 不仅在源代码上兼容,而且由于从 Swift 5 开始 ABI 就稳定了,Swift 5 及后续版本在二进制层面也是兼容的。
Swift 5.1 在 Swift 5 引入的 ABI稳定性之上又增加了模块稳定性。ABI 稳定保证了应用程序运行时的兼容性,而模块稳定性保证了编译时框架的兼容性。这意味着你可以在任意版本的编译器上使用一个第三方框架,而不是仅在构建它的版本的编译器上使用。
文中每个新特性部分都包含有 Swift Evolution 提案的编号,类似这样 [SE-00001]。你可以单击每个链接的标签来查看它的更改详情。
Note: 如果您需要重新了解 Swfit 5 的亮点,可以查看 What's New in Swift 5
Language Improvements
此版本对语言做了许多新改进,包括 不透明的结果类型(opaque result types)、函数构建器(function builders)、属性包装器(property wrappers) 等等。
1. Opaque Result Types
在 Swift 5 中,你可以使用协议作为返回类型。
创建一个新的 Playground,通过导航栏 View -> Navigators -> Show Project Navigator,打开 Project Navigator。右击 Sources 文件夹,选择 New File 并命名为 BlogPost。使用名为 BlogPost
的新协议替换该文件的内容。
public protocol BlogPost {
var title: String { get }
var author: String { get }
}
右击顶层的 playground 并选择 New Playground Page。将新页面命名为 Opaque Tutorials 并粘贴如下内容:
// 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ă")
逐步分析:
- 声明
Tutorial
的title
和author
,因为Tutorial
实现了BlogPost
协议。 - 定义一个函数
createBlogPost(title:author:)
,检查title
和author
是否有效,若有效则返回Tutorial
。 - 使用函数
createBlogPost(title:author:)
来创建swift4Tutorial
和swift5Tutorial
。
您可以重用 createBlogPost(title:author:)
的原型和逻辑来创建 Screencasts。
右击顶层的 playground 并选择 New Playground Page。将新页面命名为 Opaque Screencasts 并粘贴如下内容:
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
来创建 swift4Screencast
和 swift5Screencast
。
导航到 Sources 文件夹下的 BlogPost.swift ,使 BlogPost
文件遵守 Equatable
协议:
public protocol BlogPost: Equatable {
var title: String { get }
var author: String { get }
}
此时,你会得到一个编译错误:BlogPost
只能被用作泛型约束。这是因为 Equatable
协议有一个名为 Self
的关联类型。具有关联类型的协议不能用作类型,即使他们看起来像类型。相反,它们有点像类型占位符,表示 “它可以是遵守此类型的任何类型”。
Swift 5.1 允许你使用 opaque result types [SE-0244],来将这类协议当做常规类型。
在 Opaque Tutorials 页面,添加 some
关键字到 createBlogPost
的返回类型中,表示它会返回 BlogPost
协议的具体实现。
func createBlogPost(title: String, author: String) -> some BlogPost {
类似的,在 Opaque Screencasts 页面,使用 some
关键字告诉编译器 createBlogPost
返回某种类型的 BlogPost
:
func createBlogPost(title: String, author: String) -> some BlogPost {
你可以从 createBlogPost
返回实现了 BlogPOst
的任何具体类型,例如 Tutorial
或 Screencast
。
现在,你可以检查前面创建的 tutorails 和 screencasts 是否相同。在 Opaque Tutorials 底部,粘贴如下代码来检查 swift4Tutorial
和 swift5Tutorial
是否相等:
let sameTutorial = swift4Tutorial == swift5Tutorial
在 Opaque screencasts 底部,粘贴如下代码来检查 swift4Screencast
和 swift5Screencast
是否相等:
let sameScreencast = swift4Screencast == swift5Screencast
2. Implicit Returns From Single-Expression Functions
在 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(_:_:)
来决定 Sequence
中偶数和奇数的总和。
Swift 5.1 可省略单表达式函数中的 return
,此时它们的行为类似于单行闭包 [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 }
}
}
这样的代码更简洁,更容易遵循。
Note: 想要了解更多关于
reduce(_:_:)
函数的知识吗,可以查看这篇教程:An Introduction to Functional Programming in Swift.
3. Function Builders
Swift 5.1 使用函数构建器(function builders) 来实现构建器模式 [SE-XXXX]:
@_functionBuilder
struct SumBuilder {
static func buildBlock(_ numbers: Int...) -> Int {
return numbers.reduce(0, +)
}
}
用 @_functionBuilder
来标记 SumBuilder
,使 SumBuilder
成为函数构建器类型。函数构建器是特殊的函数类型,其中每个表达式(字面量、变量名、函数调用、if
语句等)都被分别处理并用于生成单个值。例如,你可以编写一个函数,其中每个表达式将表达式的结果添加到数组中,从而形成自己的数组字面量。
Note: 在函数构建器的标记是
@_functionBuilder
,因为此提议尚未获得批准。一旦获得批准,期望标记为@functionBuilder
。
您可以通过实现 特定名称和类型签名 的不同静态函数来创建函数构建器。buildBlock(_: T...)
是其中唯一必须实现的一个函数。还有一些函数,可以处理 if
语句、可选值和其他可被视为表达式的结构。
您可以使用 函数构造器创建的类型名,来标记 函数或者闭包:
func getSum(@SumBuilder builder: () -> Int) -> Int {
builder()
}
let gcd = getSum {
8
12
5
}
getSum
闭包 处理被传入的每个表达式(此例中是3个数字),将处理的结果传递给构建器,并将构建器隐式返回。函数构建器和它的隐式返回特性,是 SwiftUI
简洁语法的组成部分。它们还允许您创建自己领域的特定语言。
译者注:以下代码可能更好理解:
// Original source code: @TupleBuilder func build() -> (Int, Int, Int) { 1 2 3 } // This code is interpreted exactly as if it were this code: func build() -> (Int, Int, Int) { let _a = 1 let _b = 2 let _c = 3 return TupleBuilder.buildBlock(_a, _b, _c) }
4. Property Wrappers
在 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
在 settings
中获取并设置给定 key 的值。在 Swift 5.1 中可通过属性包装器(Property wrappers) [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
来获取和设置settings
中的key
。 - 使用
@SettingsWrapper
标记isSwift
和isLatestVersion
,并实现对应的属性包装器。
5. Synthesizing Default Values for Initializers in Structures
Swift 5 默认不会在结构体中为属性设置初始化的值,所以我们需要像这样定义初始化值:
struct Author {
let name: String
var tutorialCount: Int![Diffing collections like a pro in Swift 5.1!.png](https://upload-images.jianshu.io/upload_images/1610911-ff96a7fe1bb157ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
init(name: String, tutorialCount: Int = 0) {
self.name = name
self.tutorialCount = tutorialCount
}
}
let author = Author(name: "George")
我们给构造初始化器提供默认参数 tutorialCount
= 0。因此在创建 author
时仅传递参数 name
,并且 author.tutorialCount
默认为 0。
Swift 5.1 让我们可以直接为结构体属性设置默认值,所以我们不必再通过自定义初始化器达到此效果 [SE-0242]:
struct Author {
let name: String
var tutorialCount = 0
}
代码更干净简洁了。
6. Self for Static Members
在 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()
如上所示,Editor
命名可能会发生变化,且命名冗长,难以维护和阅读。
在 Swift 5.1 [SE-0068] 中,你可以使用 Self
来替换实例方法中的 Editor
引用:
struct Editor {
static func reviewGuidelines() {
print("Review editing guidelines.")
}
func edit() {
Self.reviewGuidelines()
print("Ready for editing!")
}
}
现在,使用 Self
来调用 reviewGuidelines()
方法。
7. Creating Uninitialized Arrays
在 Swift 5.1 [SE-0245] 中,我们可以创建未初始化的数组了:
// 1
let randomSwitches = Array<String>(unsafeUninitializedCapacity: 10) {
buffer, initializedCount in
// 2
for i in 0..<5 {
buffer[i] = Bool.random() ? "on" : "off"
}
// 3
initializedCount = 5
}
逐步分析:
- 使用
init(unsafeUninitializedCapacity:initializingWith:)
方法创建一个具有一定容量的数组randomSwitches
。 - 遍历
randomSwitches
并使用random()
设置每个元素的状态。 - 设置
randomSwitches
初始化了的元素的数量。
8. Diffing Ordered Collections
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
包括了从 Swift 1 到现在的所有操作系统版本,从旧到新排序。
answers
逆序列出了它们,并增添或删减了部分元素。
求差异集合的Api要求我们使用 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:)
方法,从 operatingSystems
和 answers
中取得它俩之间的差异 differences
,并且使用 applying()
方法将此差异应用到 answers
中,最终得到和 operatingSystems
相同元素和顺序的数组 sameAnswers
。
或者,你也可以不使用 applying()
方法,而是手动实现:
// 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:)
,则向answers
数组中位于offset
处 添加元素element
。如果associatedWith
值不为nil
,则将此次操作视为answers
数组内部的元素位置移动,而不是从外部新加的元素。 - 如果
change
类型是.remove(offset:element:associatedWith:)
,则向answers
数组中位于offset
处 删除元素element
。如果associatedWith
值不为nil
,则将此次操作视为answers
数组内部的元素位置移动,而不是将该元素从数组中删除了。
9. Static and Class Subscripts
Swift 5.1 允许我们在类型(class / struct / enum) 中声明 static 和 class subscripts [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
标记File
,以使File
支持点语法的动态成员查找。 - 创建一个静态下标方法,返回默认值或自定义的值。
- 定义一个 class 类型的动态成员查找方法。
- 用相应的语法调用这两种下标。
Note: 想要了解更多关于 Swift subscripts 的知识吗?请看这里 Custom Subscripts in Swift.
10. Dynamic Member Lookup for Keypaths
Swift 5.1 实现了基于 keypaths 的动态成员查找方法 [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
逐步分析:
- 为
Point
声明x
和y
属性。 - 用
@dynamicMemberLookup
标记Circle
,以使Circle
支持点语法的动态成员查找。 - 创建一个 使用 keypaths 的泛型下标方法,用来获取
Circle
中center
的属性。 - 使用动态成员查找来代替keypaths,直接在
circle
上调用center
的属性。
Note: 想要了解更多关于动态成员查找的知识吗?请看 Dynamic Features in Swift.
11. Keypaths for Tuples
在 Swift 5.1 中,我们可以为元组使用 keypaths 了:
// 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]
逐步分析:
- 为
Instrument
声明brand
,year
和details
属性。 - 使用 keypaths,从
instrument
的details
属性中获取type
和pitch
。
12. Equatable and Hashable Conformance for Weak and Unowned Properties
Swift 5.1 为有 weak
和 unowned
存储属性的结构体 自动合成 Equatable
和 Hashable
一致性。
假设有如下两个类:
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)
}
}
Key
和 Chord
都遵守 Equatable
和 Hashable
协议,因为它们实现了 ==(lhs:rhs:)
和 hash(into:)
方法。
如果你在结构体中使用这种类型,Swift 5.1 将自动为你合成 Hashable
一致性,而不需要你手动实现 ==(lhs:rhs:)
和 hash(into:)
方法:
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]]
Tune
是遵守 Equatable
和 Hashable
协议的,因为 Tune
的属性 key
和 chord
是遵守 Equatable
和 Hashable
的。
因为 Tune
遵守 Hashable
协议,所以你可以比较 tune
和 chordlessTune
,把它们加入 tuneSet
集合中,并使用它们作为 tuneDictionary
字典的keys。
13. Ambiguous Enumeration Cases
Swift 5.1 会为模棱两可的枚举类型生成警告⚠️。
// 1
enum TutorialStyle {
case cookbook, stepByStep, none
}
// 2
let style: TutorialStyle? = .none
逐步分析:
- 为
TutorialStyle
定义不同的 style。 - Swift 发出一个警告,因为在这儿,
.none
对于编译器来说是不明确的,它可以是Optional.none
,也可以是TutorialStyle.none
。
14. Matching Optional Enumerations Against Non-optionals
在 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
}
逐步分析:
- 为
TutorialStatus
声明所有可能的状态。 - 使用可选模式匹配来匹配
status
,因为我们把它定义为了可选值。
Swift 5.1 移除了此例中的可选模式匹配:
switch status {
case .written:
print("Ready for editing!")
case .edited:
print("Ready to publish!")
case .published:
print("Live!")
case .none:
break
}
代码更加清晰,且容易理解了。
Note: 想要了解更多关于模式匹配的知识吗?请看这里 Pattern Matching in Swift.
15. New Features for Strings
Swift 5.1 为字符串添加了一些急需的功能 [SE-0248]:
UTF8.width("S")
UTF8.isASCII(83)
现在,你可以确定 Unicode 标量值得 UTF-8 编码宽度,并检查给定的码元是否是一个 ASCII 标量。查看提案中其他可能使用的APIs [SE-0248].
16. Contiguous Strings
Swift 5.1 实现了对连续字符串的重要的更改 [SE-0247]:
var string = "Swift 5.1"
if !string.isContiguousUTF8 {
string.makeContiguousUTF8()
}
你可以使用 isContiguousUTF8
来检查 UTF-8 编码的字符串在内存上是否是连续的,并使用 makeContiguousUTF8()
方法来使它成为连续的字符串。去提案中查看你还可以通过连续的字符串做些什么 [SE-0247]。
17. Converting Tuple Types
Swift 5.1 改进了元组类型的转换:
let temperatures: (Int, Int) = (25, 30)
let convertedTemperatures: (Int?, Any) = temperatures
现在可以把 temperatures
赋值给 convertedTemperatures
,因为此例中 (Int, Int)
可以转换为 (Int?, Any)
。
18. Tuples with Duplicate Labels
在 Swift 5 中,可以用重复的标签声明元组中的元素:
let point = (coordinate: 1, coordinate: 2)
point.coordinate
此例中,我们并不能明确的知道 point.coordinate
是返回了第一个还是第二个元素。因此 Swift 5.1 不允许元组中声明重复的标签。
19. Overloading Functions with Any Parameters
对于只有一个参数的重载函数,Swift 5 更倾向于调用带 Any
参数的函数 而非 带泛型参数的函数:
func showInfo(_: Any) -> String {
return "Any value"
}
func showInfo<T>(_: T) -> String {
return "Generic value"
}
showInfo("Swift")
在 Swift 5 中, showInfo()
将返回 "Any value"
。
而在 Swift 5.1 中, showInfo()
将返回 Generic value
。
20. Type Aliases for Autoclosure Parameters
在 Swfit 5 中,不能为自动闭包参数 @autoclosure
声明别名:
struct Closure<T> {
func apply(closure: @autoclosure () -> T) {
closure()
}
}
函数 apply(closure:)
中的参数 closure
使用了自动闭包签名。
在 Swift 5.1 中,可以在函数 apply(closure:)
中使用自动闭包类型参数的别名:
struct Closure<T> {
typealias ClosureType = () -> T
func apply(closure: @autoclosure ClosureType) {
closure()
}
}
这次,函数 apply(closure:)
的参数 closure
使用了 ClosureType
类型来声明。
21. Returning Self From Objective-C methods
在 Swift 5 中,如果 **class ** 中含有 @objc
的方法并返回了 Self
,则该 class 必须继承于 NSObject
:
class Clone: NSObject {
@objc func clone() -> Self {
return self
}
}
例中 Clone
继承于 NSObject
因为 clone()
方法返回了 Self
。
在 Swift 5.1 中,将不再必须继承 NSObject
:
class Clone {
@objc func clone() -> Self {
self
}
}
现在 Clone
不必继承任何类了。
22. Stable ABI Libraries
您可以在 Swift 5.1 中使用 -enable-library-evolution
来更改库类型,而不破坏它的 ABI。标记为 @frozen
的结构体和枚举不能 添加、删除 或 重新排序存储属性和方法 [SE-0260]。
最后
Swift 5.1 在 Swift 5 的基础上添加了许多很棒的功能。它也为 Swift语言带来了模块稳定性,并实现了 WWDC 中引入的新框架,如:SwiftUI 和 Combnine。
您可以在 Swift 官方的 CHANGELOG 或者 Swift standard library differences 中了解到更多关于 Swift 语言的变动。
您也可以在 Swift Evolution 中查看Swift下个版本会带来什么新特性。在这里,您可以为当前在审阅的提案提供反馈,甚至可以自己提出提案,为Swift语言舔砖加瓦!