版本记录
版本号 | 时间 |
---|---|
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(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 {
let center: T
let radius: Int
// 3
subscript(dynamicMember keyPath: KeyPath) -> 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) -> String {
return "Generic value"
}
showInfo("Swift 5")
在这种case
下,showInfo()
返回“Any value”
。Swift 5.1
以相反的方式工作:
func showInfo(_: Any) -> String {
"Any value"
}
func showInfo(_: T) -> String {
"Generic value"
}
showInfo("Swift 5.1")
showInfo()
这次返回“Generic value”
。
4. Type Aliases for Autoclosure Parameters
你不能在Swift 5
中为@autoclosure
参数声明类型别名(type aliases)
:
struct Closure {
func apply(closure: @autoclosure () -> T) {
closure()
}
}
apply(closure :)
在这种情况下使用autoclosure
签名进行closure
。 您可以在Swift 5.1
中的apply(closure :)
原型中使用类型别名:
struct Closure {
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新变化,感兴趣的给个赞或者关注~~~