版本记录
| 版本号 | 时间 |
|---|---|
| V1.0 | 2019.08.01 星期四 |
前言
几乎随着每一版iOS新系统的发布,Swift都会有所改变,加入了更多的特性,下面我们就一起走进看一下相关的变化。
开始
首先,看下主要内容
主要内容:Swift 5.1的新变化。
接着,看下写作环境
Swift 5, iOS 13, Xcode 11
好消息:Swift 5.1现在可以在Xcode 11 beta中使用!此版本带来了模块稳定性(module stability),并通过改进了语言的重要特征。在本教程中,您将了解Swift 5.1中的新功能。您需要Xcode 11 beta才能使用Swift 5.1,因此请在开始之前安装它。
Swift 5.1与Swift 5兼容。由于ABI stability,它还与Swift 5以及未来版本的Swift二进制兼容。
Swift 5.1在Swift 5中引入的ABI stability之上增加了模块稳定性。虽然ABI稳定性在运行时负责应用程序兼容性,但模块稳定性使编译时的库兼容性成为可能。这意味着您可以将第三方框架与任何编译器版本一起使用,而不是仅使用它构建的版本。
每个教程部分都包含Swift Evolution建议编号,例如[SE-0001]。您可以通过单击每个提案的链接标记来浏览每个更改。
我建议您通过在playground上尝试新功能来学习本教程。启动Xcode 11并转到File ▸ New ▸ Playground。选择iOS作为平台,选择Blank作为模板。将其命名并将其保存在您想要的位置。那么就是时候开始了!
Language Improvements
此版本中有许多语言改进,包括不透明的结果类型,函数构建器,属性包装器等。
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。 重命名新的playground页面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ă")
这一步一步:
- 1) 声明
Tutorial的title和author,因为Tutorial实现了BlogPost。 - 2) 检查
title和author是否有效,如果测试成功,则从createBlogPost(title:author :)返回Tutorial。 - 3) 使用
createBlogPost(title:author :)创建swift4Tutorial和swift5Tutorial。
您可以重复使用createBlogPost(title:author :)的原型和逻辑来创建截屏视频。
右键单击顶层playground并选择New Playground Page。 重命名新的playground页面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(title:author :)来创建swift4Screencast和swift5Screencast。
导航到Sources文件夹中的BlogPost.swift,并使BlogPost符合Equatable。
public protocol BlogPost: Equatable {
var title: String { get }
var author: String { get }
}
此时,您将收到一个错误,即BlogPost只能用作generic constraint。 这是因为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。
现在,您可以检查以前创建的教程和截屏是否相同。 在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 }
}
}
这次代码更清晰,更容易理解。
3. Function Builders
Swift 5.1使用函数构建器(function builders)来实现构建器模式 builder pattern[SE-XXXX]:
@_functionBuilder
struct SumBuilder {
static func buildBlock(_ numbers: Int...) -> Int {
return numbers.reduce(0, +)
}
}
使用@_functionBuilder注释SumBuilder,使其成为函数构建器类型。 函数构建器是特殊类型的函数,其中每个表达式(文字,变量名,函数调用,if语句等)单独处理并用于生成单个值。 例如,您可以编写一个函数,其中每个表达式将该表达式的结果添加到数组中,从而创建您自己的数组文字。
注意:在Xcode测试版中,函数构建器的注释是
@_functionBuilder,因为此提议尚未获得批准。 一旦获得批准,期望注释成为@functionBuilder。
您可以通过实现具有特定名称和类型签名的不同静态函数来创建函数构建器。 buildBlock(_:T ...)是唯一需要的。 还有一些函数可以处理if语句,选项和其他可以作为表达式处理的结构。
您可以通过使用类名注释函数或闭包来使用函数构建器:
func getSum(@SumBuilder builder: () -> Int) -> Int {
builder()
}
let gcd = getSum {
8
12
5
}
传递给getSum的闭包计算每个表达式(在本例中为三个数字),并将这些表达式的结果列表传递给构建器。 函数构建器和隐式返回是SwiftUI干净语法的构建块。 它们还允许您创建自己的特定于域的语言。
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中获取并设置给定键的值。 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
}
这就是上面代码的工作原理:
- 1) 使用
@propertyWrapper注释SettingsWrapper,使其成为属性包装器类型。 - 2) 使用
wrappedValue获取并设置settings中的key。 - 3) 标记
isSwift和isLatestVersion为@SettingsWrapper用相应的包装器实现它们。

5. Synthesizing Default Values for Initializers in Structures
默认情况下,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")
如果author已经通过了他的试用并加入了网站上的教程团队,那么你将这里设置tutorialCount为0。
Swift 5.1允许您直接为结构属性设置默认值,因此不再需要自定义初始值设定项[SE-0242]:
struct Author {
let name: String
var tutorialCount = 0
}
这次代码更简洁,更简单。
6. Self for Static Members
您不能使用Self在Swift 5中引用数据类型的静态成员,因此您必须使用类型名称:
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中使用Self重写整个东西[SE-0068]:
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: 5) {
buffer, count in
// 2
for i in 0..<5 {
buffer[i] = Bool.random() ? "on" : "off"
}
// 3
count = 5
}
逐步完成上面的代码:
- 1) 使用
init(unsafeUninitializedCapacity:initializingWith :)创建具有特定初始容量的randomSwitches。 - 2) 循环遍历
randomSwitches并使用random()设置每个开关状态。 - 3) 设置
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从最旧到最新排列的所有macOS版本。 answers在添加和删除其中一些时以相反的顺序列出它们。
差异集合需要您使用#if swift(> =)检查最新的Swift版本,因为所有diffing方法仅标记为@available,仅适用于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。
或者,您可以手动执行此操作:
// 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()来确定differences中的移动并遍历它们。 - 2) 如果
change为.insert(offset:element:associatedWith :),则将offset处的element添加到answers中,如果associatedWith不为nil,则将插入视为removal。 - 3) 如果
change为.remove(offset:element:associatedWith :),则删除answers处offset答案的element,如果associatedWith不为nil,则考虑删除removal。

9. Static and Class Subscripts
Swift 5.1允许您在[SE-0254]中声明static和class subscripts:
// 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) 将
File标记为@dynamicMemberLookup以启用custom subscripts的点语法。 - 2) 创建一个静态
subscript,返回File的默认路径或自定义路径。 - 3) 使用动态成员查找定义上一个
subscript的类版本。 - 4) 使用相应的语法调用两个
subscript。
注意:想要了解有关Swift中下标的更多信息? 查看下标教程:Swift中的自定义下标。
10. Dynamic Member Lookup for Keypaths
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) 为
Point声明x和y。 - 2) 使用
@dynamicMemberLookup注释Circle以为其subscripts启用点语法。 - 3) 创建一个通用
subscripts,使用键路径从Circle访问center属性。 - 4) 使用动态成员查找而不是键路径调用
circle上的调用center属性。
11. Keypaths for Tuples
您可以在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) 声明
Instrument的rand, year, details。 - 2) 使用关键路径从
instrument中的details中获取type, pitch。
12. Equatable and Hashable Conformance for Weak and Unowned Properties
Swift 5.1自动为具有weak和unowned存储属性的结构合成Equatable和Hashable 的 conformance。
假设你有两个类:
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将能够合成Hashable conformance:
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,因为key和chord是Equatable和Hashable。
因为它是Hashable,你可以将tune与chordlessTune进行比较,将它们添加到tuneSet并将它们用作tuneDictionary的键。
13. Ambiguous Enumeration Cases
Swift 5.1为不明确的枚举cases生成警告:
// 1
enum TutorialStyle {
case cookbook, stepByStep, none
}
// 2
let style: TutorialStyle? = .none
下面就是工作原理:
- 1) 为
TutorialStyle定义不同的样式。 - 2)
Swift会发出警告,因为编译器不清楚.none在case: Optional.none和TutorialStyle.none的含义是什么。
14. Matching Optional Enumerations Against Non-optionals
在Swift 5中,您可以使用optional pattern将non-optionals与的可选枚举optional enumerations进行匹配:
// 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) 使用可选模式打开
status,因为您将其定义为可选模式。
在这种情况下,Swift 5.1删除了可选的模式(optional pattern):
switch status {
case .written:
print("Ready for editing!")
case .edited:
print("Ready to publish!")
case .published:
print("Live!")
case .none:
break
}
这段代码更清晰,更容易理解。
15. New Features for Strings
Swift 5.1为字符串添加了一些急需的功能[SE-0248]:
UTF8.width("S")
UTF8.isASCII(83)
在这里,您确定Unicode标量值的UTF-8编码宽度,并检查给定的代码单元是否表示ASCII标量。 查看您可以使用的其他API的proposal。
16. Contiguous Strings
Swift 5.1对连续字符串(contiguous strings)[SE-0247]实现重要更改:
var string = "Swift 5.1"
if !string.isContiguousUTF8 {
string.makeContiguousUTF8()
}
您检查UTF-8编码的字符串是否与isContiguousUTF8连续,并使用makeContiguousUTF8()来实现,如果不是。 看一下提案(proposal),看看你可以用连续的字符串做些什么。
Miscellaneous Bits and Pieces
您应该了解Swift 5.1中的一些其他功能:
1. Converting Tuple Types
Swift 5.1改进了元组类型的转换:
let temperatures: (Int, Int) = (25, 30)
let convertedTemperatures: (Int?, Any) = temperatures
您可以为convertedTemperatures赋值为temperatures,因为在这种情况下您可以将(Int,Int)转换为(Int?,Any)。
2. Tuples with Duplicate Labels
您可以在Swift 5中声明带有重复标签的元组:
let point = (coordinate: 1, coordinate: 2)
point.coordinate
在这种情况下,不清楚coordinate是否从point返回第一个或第二个元素,因此Swift 5.1删除了元组的重复标签。
3. 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 5")
在这种case下,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”。
4. Type Aliases for Autoclosure Parameters
你不能在Swift 5中为@autoclosure参数声明类型别名(type aliases):
struct Closure<T> {
func apply(closure: @autoclosure () -> T) {
closure()
}
}
apply(closure :)在这种情况下使用autoclosure签名进行closure。 您可以在Swift 5.1中的apply(closure :)原型中使用类型别名:
struct Closure<T> {
typealias ClosureType = () -> T
func apply(closure: @autoclosure ClosureType) {
closure()
}
}
apply(closure :)这次使用ClosureType进行闭包closure。
5. Returning Self From Objective-C methods
如果你的类包含一个在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
}
}
Clone这次不必继承任何东西。
6. Stable ABI Libraries
您可以在Swift 5.1中使用-enable-library-evolution来更改库类型而不会破坏其ABI。 标记为@frozen的结构和枚举不能添加,删除或重新排序存储的属性和cases [SE-0260]。
Swift 5.1为Swift 5中已经引入的功能添加了许多不错的功能。它还为语言带来了模块稳定性(module stability),并实现了WWDC中引入的新框架(如SwiftUI和Combine)所使用的复杂范例。
您可以在官方 Swift CHANGELOG 或 Swift standard library differences上阅读有关此Swift版本更改的更多信息。
后记
本篇主要讲述了Swift 5.1新变化,感兴趣的给个赞或者关注~~~

