好消息! Swift 5 最终在 Xcode 10.2 中可用! 此版本带来了 ABI**的稳定性,并通过一些期待已久的功能改进了语言。
注意: 当前版本为Swift 5, iOS 12, Xcode 10
在本教程中,您将了解 Swift 5 中最重要的更改. Swift 5 需要 Xcode 10.2 ,因此请确保在开始之前已安装好。
入门
Swift 5 与 Swift 4.2 兼容,但与早期的 Swift 版本不兼容。但是,由于 ABI 稳定性,未来版本将与 Swift 5 二进制兼容。
ABI稳定性支持使用不同 Swift 版本编译的应用程序和库之间的二进制兼容性。 Swift 标准库和运行时嵌入在操作系统中,因此应用程序不会在任何平台上分发自己的库副本。这导致更好的工具解耦和 OS 集成。
您还需要 ABI 稳定性来分发跨多个 Swift 版本的二进制框架。这需要模块格式稳定性,这可以稳定包含编译器的框架公共接口表示的模块文件。
您将在本教程的每个部分中找到类似**[SE-0001]**的 Swift Evolution 建议编号。您可以浏览每个提案链接,详细了解每项新变化。
遵循本教程的最佳方法是在操场上试用新功能。
启动 Xcode 10.2 并选择 File ▸ New ▸ Playground 。将平台设置为 iOS ,将模板设置为 Blank 。命名并将其保存在您想要的任何地方开始的时候了!
注意:需要快速提醒一下 Swift 4.2 亮点? 查看 Swift 4.2 教程: Swift 4.2中有哪些新功能?
语言改进
Swift 5 中有许多语言功能,例如动态可调用类型,处理未来的枚举等等。
多种Integer的测试
在 Swift 4.2 中,您可以使用余数运算符确定数字是否是另一个的倍数:
let firstNumber = 4
let secondNumber = 2
if secondNumber != 0 && firstNumber % secondNumber == 0 {
print("\(secondNumber) * \(firstNumber / secondNumber) = \(firstNumber)")
}
复制代码
这段代码的工作原理:
- 检查 secondNumber 是否为 0 。
- 检查将 firstNumber 除以 secondNumber 会返回 0 的余数。
- 执行除法运算。
您必须检查
secondNumber
是否为
0
,因为
%
运算符会抛出错误。
Swift 5
通过向
BinaryInteger
SE-0225
添加
isMultiple(of :)
来简化这一过程:
if firstNumber.isMultiple(of: secondNumber) {
print("\(secondNumber) * \(firstNumber / secondNumber) = \(firstNumber)")
}
复制代码
即使你将参数传递给
0
,
isMultiple(of :)
也可以工作,结果代码更清晰。
逃避原始字符串
Swift 4.2 使用转义序列来表示字符串中的反斜杠和引号:
let escape = "You use escape sequences for \"quotes\"\\\"backslashes\" in Swift 4.2."
let multiline = """
You use escape sequences for \"\"\"quotes\"\"\"\\\"\"\"backslashes\"\"\"
on multiple lines
in Swift 4.2.
"""
复制代码
Swift 5
添加原始字符串。 您在字符串的开头和结尾添加
#
,这样您就可以使用反斜杠和引号而不会出现问题。
SE-0200
:
let raw = #"You can create "raw"\"plain" strings in Swift 5."#
let multiline = #"""
You can create """raw"""\"""plain""" strings
on multiple lines
in Swift 5.
"""#
复制代码
在原始字符串中使用字符串插值时,必须在反斜杠后使用井号:
let track = "Nothing Else Matters"
print(#"My favorite tune\song is \#(track)."#)
复制代码
在某些情况下,您需要在字符串的开头和结尾使用多个
#
:
let hashtag = ##"You can use the Swift "hashtag" #swift in Swift 5."##
复制代码
在上面的代码中,您在
hashtag
的开头和结尾添加
##
,以便您可以在字符串中表示
#
。 字符串开头使用的#s数必须与其末尾的数字相匹配。
在 Swift 4.2 中,您可以在正则表达式中转义反斜杠,如下所示:
// 1
let versions = "3 3.1 4 4.1 4.2 5"
let range = NSRange(versions.startIndex..., in: versions)
// 2
let regex = try! NSRegularExpression(pattern: "\\d\\.\\d")
// 3
let minorVersions = regex.matches(in: versions, range: range)
// 4
minorVersions.forEach { print(versions[Range($0.range, in: versions)!]) }
复制代码
以下是此代码的工作原理:
- 声明版本并定义覆盖整个字符串的范围。
- 定义一个匹配版本中所有次要Swift版本的正则表达式。
-
使用
matches(in: options: range:)
确定次要版本范围。 - 使用范围从版本中获取次要版本。
Swift 5 使用原始字符串简化了正则表达式:
let regex = try! NSRegularExpression(pattern: #"\d\.\d"#)
复制代码
在此代码中,您使用反斜杠数量的一半来编写正则表达式,因为您不需要在原始字符串中转义反斜杠。
注意:需要有关正则表达式如何在 Swift 中工作的更多详细信息? 查看正则表达式教程: 正则表达式简介 。
使用新字符属性
在处理字符时, Swift 4.2 需要常见任务的变通方法:
let id = "ID10"
var digits = 0
id.forEach { digits += Int(String($0)) != nil ? 1 : 0 }
print("Id has \(digits) digits.")
复制代码
在此代码中,首先将每个字符转换为 String ,然后再转换为 Int ,以确定 id 的位数。
但是, Swift 5 为 Character 添加了属性,使角色更易于使用 SE-0221 :
id.forEach { digits += $0.isNumber ? 1 : 0 }
复制代码
在这种情况下,您使用 isNumber 来检查每个字符是否都是数字。 查看您可以使用的其他属性的提案。
使用新的Unicode标量属性
在 Swift 4.2 中,您为 unicode 标量实现了文本处理算法,如下所示:
let username = "bond007"
var letters = 0
username.unicodeScalars.forEach {
letters += (65...90) ~= $0.value || (97...122) ~= $0.value ? 1 : 0
}
print("Username has \(letters) letters.")
复制代码
在此代码中,您可以通过检查每个字符的 unicode 标量是代表小写字母还是大写字母来计算用户名的字母数。
Swift 5 为 unicode 标量添加了属性,简化了文本处理 SE-0211 :
username.unicodeScalars.forEach { letters += $0.properties.isAlphabetic ? 1 : 0 }
复制代码
在此代码中,您使用 isAlphabetic 检查每个字符是否为数字。 链接的提案显示您可以检查的所有属性。
删除子序列
Swift 4.2 从 Sequence 自定义点返回 SubSequence ,如下所示:
extension Sequence {
func remove(_ s: String) -> SubSequence {
guard let n = Int(s) else {
return dropLast()
}
return dropLast(n)
}
}
let sequence = [5, 2, 7, 4]
sequence.remove("2") // [5, 2]
sequence.remove("two") // [5, 2, 7]
复制代码
在这种情况下,如果
s
是
Int
或最后一个元素,
remove(_ :)
将删除序列中的最后n个元素。
Swift 5 用序列中的具体类型替换 SubSequence SE-0234 :
extension Sequence {
func remove(_ s: String) -> [Element] {
guard let n = Int(s) else {
return dropLast()
}
return dropLast(n)
}
}
复制代码
在此代码中,
remove(_ :)
返回
[Element]
,因为
dropLast()
和
dropLast(_ :)
返回
[Element]
。
字典更新
Swift 5 为词典带来了期待已久的改进:
压缩字典
Swift 4.2 使用 mapValues , filter 和 reduce 来过滤字典中的 nil 值,如下所示:
let students = ["Oana"
: "10", "Nori": "ten"]
let filterStudents = students.mapValues(Int.init)
.filter { $0.value != nil }
.mapValues { $0! }
let reduceStudents = students.reduce(into: [:]) { $0[$1.key] = Int($1.value) }
复制代码
此代码使用带有 filter 或 reduce 的 mapValues 来确定学生的有效成绩。 这两种方法都需要多次字典传递并使代码复杂化。
Swift 5
使用
compactMapValues(_ :)
来获得更有效的解决方案
SE-0218
:
let mapStudents = students.compactMapValues(Int.init)
复制代码
它以更少的代码行完成同样的事情,整洁!
重命名字典文字
Swift 4.2 使用 DictionaryLiteral 来声明字典,如下所示
let pets: DictionaryLiteral = ["dog": "Sclip", "cat": "Peti"]
复制代码
DictionaryLiteral 不是字典或文字。 这是一个键值对列表。
Swift 5 将 DictionaryLiteral 重命名为 KeyValuePairs SE-0214 :
let pets: KeyValuePairs = ["dog": "Sclip", "cat": "Peti"]
复制代码
数字协议更新
Swift 4.2 为向量实现数值:
// 1
struct Vector {
let x, y: Int
init(_ x: Int, _ y: Int) {
self.x = x
self.y = y
}
}
// 2
extension Vector: ExpressibleByIntegerLiteral {
init(integerLiteral value: Int) {
x = value
y = value
}
}
// 3
extension Vector: Numeric {
var magnitude: Int {
return Int(sqrt(Double(x * x + y * y)))
}
init?<T>(exactly value: T) {
x = value as! Int
y = value as! Int
}
static func +(lhs: Vector, rhs: Vector) -> Vector {
return Vector(lhs.x + rhs.x, lhs.y + rhs.y)
}
static func +=(lhs: inout Vector, rhs: Vector) {
lhs = lhs + rhs
}
static func -(lhs: Vector, rhs: Vector) -> Vector {
return Vector(lhs.x - rhs.x, lhs.y - rhs.y)
}
static func -=(lhs: inout Vector, rhs: Vector) {
lhs = lhs - rhs
}
static func *(lhs: Vector, rhs: Vector) -> Vector {
return Vector(lhs.x * rhs.y, lhs.y * rhs.x)
}
static func *=(lhs: inout Vector, rhs: Vector) {
lhs = lhs * rhs
}
}
// 4
extension Vector: CustomStringConvertible {
var description: String {
return "(\(x) \(y))"
}
}
复制代码
以下是此代码的工作原理:
-
为 Vector 声明 x , y 和
init(_: _ :)
。 -
实现
init(integerLiteral :)
以使 Vector 符合 ExpressibleByIntegerLiteral 作为数字一致性的要求。 -
通过定义向量的大小,声明
init(exactly:)
并实现+(lhs: rhs :)
,+=(lhs: rhs :)
,-(lhs: rhs :)
,-=(lhs: rhs: )
,*(lhs:rhs :)
,* =(lhs: rhs :)
。 -
实现描述以使 Vector 符合 CustomStringConvertible 。
上面的代码使您可以轻松地使用向量:
var first = Vector(1, 2) // (1,2)
let second = Vector(3, 4) // (3,4)
let third = first + second // (4,6)
first += second // (4,6)
let fourth = first - second // (1,2)
first -= second // (1,2)
复制代码
Swift 5 实现了向量的 AdditiveArithmetic ,因为您无法定义 2D 向量的叉积 SE-0233 。 它不需要 ExpressibleByIntegerLiteral 一致性:
extension Vector: AdditiveArithmetic {
static var zero: Vector {
return Vector(0, 0)
}
static func +(lhs: Vector, rhs: Vector) -> Vector {
return Vector(lhs.x + rhs.x, lhs.y + rhs.y)
}
static func +=(lhs: inout Vector, rhs: Vector) {
lhs = lhs + rhs
}
static func -(lhs: Vector, rhs: Vector) -> Vector {
return Vector(lhs.x - rhs.x, lhs.y - rhs.y)
}
static func -=(lhs: inout Vector, rhs: Vector) {
lhs = lhs - rhs
}
}
复制代码
在这段代码中,你通过定义零并实现
+(lhs: rhs :)
,
+=(lhs: rhs :)
,
-(lhs: rhs :)
,
- =(lhs: rhs :)
来使
Vector
符合
AdditiveArithmetic
。
注意:想要了解有关 Swift 中运算符重载的更多信息? 查看运算符重载教程: 在Swift中重载自定义运算符 。
字符串插值更新
Swift 4.2 通过插入段实现字符串插值:
let language = "Swift"
let languageSegment = String(stringInterpolationSegment: language)
let space = " "
let spaceSegment = String(stringInterpolationSegment: space)
let version = 4.2
let versionSegment = String(stringInterpolationSegment: version)
let string = String(stringInterpolation: languageSegment, spaceSegment, versionSegment)
复制代码
在此代码中,编译器首先包装每个文本段,然后使用
init(stringInterpolationSegment :)
插入一个。 然后,它用
init(stringInterpolation :)
将所有段包装在一起。
Swift 5 采用了完全不同的方法 SE-0228 :
// 1
var interpolation = DefaultStringInterpolation(
literalCapacity: 7,
interpolationCount: 1)
// 2
let language = "Swift"
interpolation.appendLiteral(language)
let space = " "
interpolation.appendLiteral(space)
let version = 5
interpolation.appendInterpolation(version)
// 3
let string = String(stringInterpolation: interpolation)
复制代码
这是代码的作用:
- 使用特定容量和插值计数定义 DefaultStringInterpolation 实例。
-
调用
appendLiteral(_ :)
或appendInterpolation(_ :)
将文字和插值添加到插值中。 -
通过调用
init(stringInterpolation :)
生成最终的插值字符串。
处理未来的查点案件
Swift 4.2 无法正确处理新的枚举案例,如下所示:
// 1
enum Post {
case tutorial, article, screencast, course
}
// 2
func readPost(_ post: Post) -> String {
switch post {
case .tutorial:
return "You are reading a tutorial."
case .article:
return "You are reading an article."
default:
return "You are watching a video."
}
}
// 3
let screencast = Post.screencast
readPost(screencast) // "You are watching a video."
let course = Post.course
readPost(course) // "You are watching a video."
复制代码
以下是上面代码中发生的情况:
- 在网站上定义所有类型的博客文章。
- 要使交换机无穷尽,请添加默认值。
-
由于截屏视频和课程是视频,因此默认处理
.screencast
和.course
。
以下是处理播客在 Swift 4.2 中的工作原理:
enum Post {
case tutorial, article, podcast, screencast, course
}
let podcast = Post.podcast
readPost(podcast) // "You are watching a video."
复制代码
在此代码中,您使用默认处理
.podcast
,即使播客不是视频。
Swift 4.2
不会对此发出警告,因为该开关是详尽无遗的。
Swift 5 处理添加的枚举案例 SE-0192 :
func readPost(_ post: BlogPost) -> String {
switch post {
case .tutorial:
return "You are reading a tutorial."
case .article:
return "You are reading an article."
@unknown default:
return "You are reading a blog post."
}
}
readPost(screencast) // "You are reading a blog post."
readPost(course) // "You are reading a blog post."
readPost(podcast) // "You are reading a blog post."
复制代码
在此代码中,您将默认标记为**@unknown**,并且Swift警告您切换并非详尽无遗。 默认处理
.screencast
,
.course
和
.podcast
,因为截屏视频,课程和播客是博客文章。
将结果添加到标准库
Swift 5 将 Result 添加到标准库 SE-0235 :
// 1
enum ConnectionError: Error {
case noNetwork, noDatabase
}
// 2
let networkSuccess = Result<String, ConnectionError>.success("Network connected!")
let databaseSuccess = Result<String, ConnectionError>.success("Database connected!")
let networkFailure = Result<String, ConnectionError>.failure(.noNetwork)
let databaseFailure = Result<String, ConnectionError>.failure(.noDatabase)
let sameSuccess = networkSuccess == databaseSuccess
let sameFailure = networkFailure == databaseFailure
let success: Set = [networkSuccess, databaseSuccess]
let failure: Set = [networkFailure, databaseFailure]
let successDictionary = [
networkSuccess: try! networkSuccess.get(),
databaseSuccess: try! databaseSuccess.get()
]
let failureDictionary = [
networkFailure: ConnectionError.noNetwork,
databaseFailure: ConnectionError.noDatabase
]
复制代码
以下是此代码的工作原理:
- 声明最常见的连接错误。
- 比较连接结果,将它们添加到集合中。 您使用这些集作为字典的键,因为 Result 实现了 Equatable 和 Hashable 。
从不符合Equatable和Hashable
Swift 5 符合 Never to Equatable 和 Hashable SE-0215 :
let alwaysSucceeds = Result<String, Never>.success("Network connected!")
let neverFails = Result<String, Never>.success("Database connected!")
let alwaysFails = Result<Never, ConnectionError>.failure(.noNetwork)
let neverSucceeds = Result<Never, ConnectionError>.failure(.noDatabase)
let sameValue = alwaysSucceeds == neverFails
let sameError = alwaysFails == neverSucceeds
let alwaysSuccess: Set = [alwaysSucceeds, neverFails]
let alwaysFailure: Set = [alwaysFails, neverSucceeds]
let alwaysSuccessDictionary = [
alwaysSucceeds: try! alwaysSucceeds.get(),
neverFails: try! neverFails.get()
]
let alwaysFailureDictionary = [
alwaysFails: ConnectionError.noNetwork,
neverSucceeds: ConnectionError.noDatabase
]
复制代码
在此代码中,您定义始终返回值或错误的连接结果,比较它们,将它们添加到集合并将它们用作字典键。
动态可调用类型
Swift 5 定义了可与脚本语言(如 Python 或 Ruby )互操作的动态可调用类型 SE-0216 :
// 1
@dynamicCallable
class DynamicFeatures {
// 2
func dynamicallyCall(withArguments params: [Int]) -> Int? {
guard !params.isEmpty else {
return nil
}
return params.reduce(0, +)
}
func dynamicallyCall(withKeywordArguments params: KeyValuePairs<String, Int>) -> Int? {
guard !params.isEmpty else {
return nil
}
return params.reduce(0) { $1.key.isEmpty ? $0 : $0 + $1.value }
}
}
// 3
let features = DynamicFeatures()
features() // nil
features(3, 4, 5) // 12
features(first: 3, 4, second: 5) // 8
复制代码
上面的代码如下:
-
将 DynamicFeatures 标记为**@dynamicCallable**以使其成为动态可调用类型。
-
要使 DynamicFeatures 符合**@dynamicCallable**,请实现
dynamicCallwithArguments :
)和dynamicCall(withKeywordArguments :)
。 -
使用普通语法调用功能,编译器调用
dynamicCall(withArguments :)
或dynamicCall(withKeywordArguments :)
。
Swift Package Manager更新
Swift 5 为 Swift Package Manager 添加了一些功能:
平台部署设置
Swift 5 允许您在 Package.swift SE-0236 中定义所需的最低平台部署目标版本:
let package = Package(name: “Package”, platforms: [
.macOS(.v10_14),
.iOS(.v12),
.tvOS(.v12),
.watchOS(.v5)
])
复制代码
您可以在
SupportedPlatform
中使用
macOS()
,
iOS()
,
tvOS()
和
watchOS()
来设置包所需的最低平台版本。
目标构建设置
Swift 5 在 Package.swift 中声明了特定于目标的构建设置。 它们定制包管理器在目标构建期间如何调用构建工具 SE-0238 。
依赖镜像
Swift 5 为 Swift Package Manager SE-0219 带来了依赖镜像。
swift package config set-mirror --package-url <package> --mirror-url <mirror>
复制代码
即使原始源不可用或被删除,镜像也允许您访问依赖项。
set-mirror 使用镜像更新依赖项,后者替换所有其他镜像。
使用 unset-mirror 从依赖项中删除镜像:
swift package config unset-mirror --package-url <package>
swift package config unset-mirror —mirror-url <mirror>
swift package config unset-mirror --all
复制代码
其他的一些改进
Swift 5 还增加了一些其他急需的功能和改进:
制作可编码范围
Swift 5 增加了 Codable 对范围的一致性 SE-0239 :