Swift 5.1终于发布了!本文将带您了解该语言在最新版本中必须提供的改进和更改。git
注意: 当前版本为Swift 5, iOS 13, Xcode 11, 如转载本文章, 请联系做者, 并给出文章的源地址github
好消息: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
Swift 5.1在Swift 5中引入的ABI稳定性之上增长了模块稳定性。虽然ABI稳定性在运行时负责应用程序兼容性,但模块稳定性使编译时的库兼容性成为可能。 这意味着您能够将第三方框架与任何编译器版本一块儿使用,而不是仅使用它构建的版本。api
每一个教程部分都包含Swift Evolution建议编号,例如**[SE-0001]**。 您能够经过单击每一个提案的连接标记来浏览每一个更改。数组
我建议您经过在操场上尝试新功能来学习本教程。 启动Xcode 11并转到File ▸ New ▸ Playground。 选择iOS做为平台,选择空白做为模板。 将其命名并将其保存在您想要的位置。 开始的时候了!闭包
注:须要重温Swift 5的亮点吗?查看Swift 5教程:Swift 5有什么新功能?app
此版本中有许多语言改进,包括不透明的结果类型,函数构建器,属性包装器等。框架
您可使用协议做为Swift 5中函数的返回类型。less
打开新的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页面Opaque教程,并粘贴在它:
// 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ă")
复制代码
一步一步来:
createBlogPost(title:author :)
返回Tutorial。createBlogPost(title:author:)
建立swift4Tutorial和swift5Tutorial。您还能够重用createBlogPost(title:author:)
的原型和逻辑来建立屏幕广播,由于屏幕广播也是隐藏在幕后的博客文章。
右键单击顶层Playground并选择New playground Page。重命名新的Playground页面Opaque的屏幕截图,并粘贴到其中:
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。
在Opaque的教程页面中,向createBlogPost的返回类型添加一些,表示它返回BlogPost的具体实现。
func createBlogPost(title: String, author: String) -> some BlogPost {
复制代码
相似地,在Opaque的屏幕显示页面中,使用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
}
复制代码
以上代码的工做原理以下:
默认状况下,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
}
复制代码
这一次代码更干净、更简单。
在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()
设置每一个开关状态。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"]
复制代码
获取操做系统和答案之间的difference(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()
肯定差别中的移动,并循环遍历它们。.insert(offset:element:associatedWith:)
,则在偏移量处向答案添加元素;若是associatedWith不是nil,则将插入视为移动。.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
复制代码
事情是这样的:
注:想了解更多关于斯威夫特下标?查看下标教程: 自定义Swift下标。
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
复制代码
一步一步来:
注:须要更多关于如何在斯威夫特动态成员查找工做的细节?查看动态特性教程: Swift中的动态特性。
你能够在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自动合成具备弱和无标识存储特性的结构的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)
}
}
复制代码
经过实现==(lhs:rhs:)
和hash(into:)
, Key和Chord都符合Equatable和Hashable。
若是你在结构体中使用这些类,Swift 5.1将可以合成Hashable:
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,由于value和chord是Equatable和Hashable。
由于它是Hashable,你能够将tune与chordlessTune进行比较,将它们添加到tuneSet并将它们用做tuneDictionary的键。
Swift 5.1为可选枚举状况生成警告:
// 1
enum TutorialStyle {
case cookbook, stepByStep, none
}
// 2
let style: TutorialStyle? = .none
复制代码
这是如何工做的:
.none
在这种状况下的含义是什么:Optional.none
或TutorialStyle.none
。您可使用可选模式将非选项与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
}
复制代码
上面的代码执行如下操做:
在这种状况下,Swift 5.1删除了可选的模式匹配:
switch status {
case .written:
print("Ready for editing!")
case .edited:
print("Ready to publish!")
case .published:
print("Live!")
case .none:
break
}
复制代码
这段代码更清晰,更容易理解。
注意:想要了解有关Swift中模式匹配的更多信息? 查看模式匹配教程:Swift中的模式匹配。
Swift 5.1为字符串添加了一些急需的功能SE-0248:
UTF8.width("S")
UTF8.isASCII(83)
复制代码
在这里,您肯定Unicode标量值的UTF-8编码宽度,并检查给定的代码单元是否表示ASCII标量。 查看您可使用的其余API的提案。
Swift 5.1对连续字符串实现重要更改SE-0247:
var string = "**Swift 5.1**"
if !string.isContiguousUTF8 {
string.makeContiguousUTF8()
}
复制代码
您检查UTF-8编码的字符串是否与isContiguousUTF8连续,并使用makeContiguousUTF8()
来实现,若是不是。 看一下提案,看看你能够用连续的字符串作些什么。
您应该了解Swift 5.1中的一些其余功能:
Swift 5.1改进了元组类型的转换:
let temperatures: (Int, Int) = (25, 30)
let convertedTemperatures: (Int?, Any) = temperatures
复制代码
您能够为convertedTemperatures分配温度,由于在这种状况下您能够将(Int, Int)
转换为(Int?, Any)
。
您能够在Swift 5中声明带有重复标签的元组:
let point = (coordinate: 1, coordinate: 2)
point.coordinate
复制代码
在这种状况下,不清楚坐标是否从点返回第一个或第二个元素,所以Swift 5.1删除了元组的重复标签。
Swift 5更喜欢任何参数而不是泛型参数,只有一个参数的函数重载:
func showInfo(_: Any) -> String {
return "Any value"
}
func showInfo<T>(_: T) -> String {
return "Generic value"
}
showInfo("Swift 5")
复制代码
在这种状况下,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”。
你不能在Swift 5中为**@autoclosure**参数声明类型别名:
struct Closure<T> {
func apply(closure: @autoclosure () -> T) {
closure()
}
}
复制代码
apply(closure :)
在这种状况下使用autoclosure声明闭包。 您能够在Swift 5.1中的apply(closure :)
原型中使用类型别名:
struct Closure<T> {
typealias ClosureType = () -> T
func apply(closure: @autoclosure ClosureType) {
closure()
}
}
复制代码
apply(closure :)
此次使用ClosureType进行闭包。
若是你的类包含一个在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
}
}
复制代码
克隆此次没必要继承任何东西。
您能够在Swift 5.1中使用**-enable-library-evolution来更改库类型而不会破坏其ABI**。 标记为**@frozen**的结构和枚举不能添加,删除或从新排序存储的属性和案例SE-0260。
您可使用本教程顶部或底部的“下载材料”连接下载最终的Playground。
Swift 5.1为Swift 5中已经引入的功能添加了许多不错的功能。它还为语言带来了模块稳定性,并实现了WWDC中引入的新框架(如SwiftUI和Combine)所使用的复杂范例。
您能够在官方Swift CHANGELOG或Swift标准库差别上阅读有关此Swift版本更改的更多信息。
您还能够查看Swift Evolution提案,了解下一版Swift的内容。 在这里,您能够为当前审核的提案提供反馈,甚至能够自行提交提案!
项目示例: 工程示例