书接前文(Swift 5 新增了什么?(上))继续介绍 Swift 5 的新增特性。
字符串插值更新
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 进行插值 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(_:)
将字符串字面量和插补值添加到interpolation
; - 调用
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."
- 定义所有的博客文章类型;
- 为了不遗漏任何枚举,给
switch
增加了default
分支; - 以
default
分支处理.screencast
和.course
,因为它们都属于视频内容。
如果后续在这段代码里增加podcasts
(播客)枚举成员:
enum Post {
case tutorial, article, podcast, screencast, course
}
let podcast = Post.podcast
readPost(podcast) // "You are watching a video."
这段代码里, .podcast
也会由 default
处理,尽量播客不是视频。Swift 4.2 不会产生任何警告因为这个 switch
是能完备处理所有的枚举成员。
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."
这段代码中 default
被 @unknown
标识,因此 Swift 会警告switch
处理不完备。
标准库增加 Result
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
]
代码解读:
- 声明常见的网络错误;
- 比较发起连接的结果,将它们加入到
Set
,你可以将这些Set
作为字典的键(key),因为Result
实现了Equatable
和Hashable
。
使 Never
遵守 Equatable
及 Hashable
Swift 5 使 Never
遵守 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
以定义成动态调用类型; - 为了遵从
@dynamicCallable
实现dynamicallyCall(withArguments:)
和anddynamicallyCall(withKeywordArguments:)
; - 用正常语法调用
features
,编译器将转化为对dynamicallyCall(withArguments:)
和anddynamicallyCall(withKeywordArguments:)
的调用。
Swift 包管理器更新
Swift 5 为包管理器增加了一些新特性:
平台部署设置
Swfit 5 允许你在 Package.swift 中定义部署目标平台所需的最低版本[ SE-0236 ]:
let package = Package(name: “Package”, platforms: [
.macOS(.v10_14),
.iOS(.v12),
.tvOS(.v12),
.watchOS(.v5)
])
可以使用 macOS()
、iOS()
、tvOS()
和 watchOS()
为 package
设置最低需要的平台版本。
目标构建设置
Swift 5 在 Package.Swift
中声明针对目标(Target)的构建设置,他们定制包管理器在目标构建期间调用构建工具的方式[ SE-0238 ].。
依赖镜像
Swift 5 的包管理器带有依赖镜像功能[ 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 还添加了其他一些急需的功能和改进:
Codable 支持范围
Swift 5 使 Codable
增加了对范围的支持[ SE-0239 ]:
let temperature = 0...10
let encoder = JSONEncoder()
let data = try! encoder.encode(temperature)
let decoder = JSONDecoder()
let temperatureRange = try! decoder.decode(ClosedRange<Int>.self, from: data)
因为 Swift 5 中 ClosedRange
默认已经实现了 Codable
,所以代码中可以通过 JSONEncoder
将 temperature
编码,并可以通过 JSONDecoder
解码。
展开多层的 Optional
Swift 4.2 使用 try?
创建多层 Optional:
extension Int {
// 1
enum DivisionError: Error {
case divisionByZero
}
// 2
func divideBy(_ number: Int) throws -> Int {
guard number != 0 else {
throw DivisionError.divisionByZero
}
return self / number
}
}
// 3
let number: Int? = 10
let division = try? number?.divideBy(2)
if let division = division,
let final = division {
print(final)
}
解读一下这段代码:
- 使用
DivisionError
扩展Int
; -
divideBy(_:)
在number
为 0 时抛出.divisionByZero
; - 因为
division
是Int??
,因此要解包装两次。
到了 Swift 5 处理就很简单了[ SE-0230 ]:
if let division = division {
print(division)
}
因为 try?
在 Swift 5 里不再创建多层 optional,所以 division
在类型是 Int?
,也就只用一次解包装了。
移除 Collection
的定制点
Swift 4.2 中可以访问 Collection
的定制点:
extension Array {
var first: Element? {
return !isEmpty ? self[count - 1] : nil
}
var last: Element? {
return !isEmpty ? self[0] : nil
}
}
let names = ["Cosmin", "Oana", "Sclip", "Nori"]
names.first // "Nori"
names.last // "Cosmin"
这段代码中,first
返回 names
数组里的最后一个名字,而 last
则返回第一个。如此将两个计算属性的行为都与预期不符,因此 Swift 5 从集合的协议中删除了它们的定制点 [ SE-0232 ]。
Key Path 标识
Swift 4.2 使用 self
来访问值:
class Tutorial {
let title: String
let author: String
init(title: String, author: String) {
self.title = title
self.author = author
}
}
var tutorial = Tutorial(title: "What's New in Swift 5.0?", author: "Cosmin Pupaza")
tutorial.self = Tutorial(title: "What's New in Swift 5?", author: "Cosmin Pupăză")
这段代码使用 .self
一次性修改 tutorial
的 title
和 author
。
Swift 5 增加了 key path 标识 来修改成员值:
tutorial[keyPath: \.self] = Tutorial(
title: "What's New in Swift 5?",
author: "Cosmin Pupăză")
这段代码使用 \.self
来更新 tutorial
。
字面量初始化强制通过
在 Swift 5 中,只要类型遵守字面量协议,它的字面量初始化函数会强制将字面量转换为该类型[ SE-0213 ]:
let value = UInt64(0xFFFF_FFFF_FFFF_FFFF)
在 Swift 4.2 中这行代码会在编译时报溢出错误(Integer literal '18446744073709551615' overflows when stored into 'Int’),而 Swift 5 则会正确处理。
构建配置更新
Swift 4.2 在编译条件中使用 >=
:
let favoriteNumber = 10
var evenNumber = true
#if !swift(>=5)
evenNumber = favoriteNumber % 2 == 0
#else
evenNumber = favoriteNumber.isMultiple(of: 2)
#endif
#if !compiler(>=5)
evenNumber = favoriteNumber % 2 == 0
#else
evenNumber = favoriteNumber.isMultiple(of: 2)
#endif
这些条件检查 Swift 版本是否大于等于 5,并在条件满足时编译相应那些代码。
Swift 5 增加 <
使得条件描述更清晰[ SE-0224 ]:
#if swift(<5)
evenNumber = favoriteNumber % 2 == 0
#else
evenNumber = favoriteNumber.isMultiple(of: 2)
#endif
#if compiler(<5)
evenNumber = favoriteNumber % 2 == 0
#else
evenNumber = favoriteNumber.isMultiple(of: 2)
#endif
对具有关联值的枚举使用可变参数
对于在 Swift 4.2 中具有关联值的枚举情况,可以使用可变参数:
enum BlogPost {
case tutorial(_: String...)
case article(_: String...)
}
使用 String...
可设置教程 (tutorial) 和文章 (article) 详细信息。 这在 Swift 5 中是不可行的,取而代之的是使用数组:
enum BlogPost {
case tutorial([String])
case article([String])
}
弃用字符串索引编码偏移量
Swift 4.2字符串使用UTF-16编码。 因此 encodedOffset
将返回 UTF-16 字符串的偏移量:
let swiftVersion = "Swift 4.2"
let offset = swiftVersion.endIndex.encodedOffset
这里可以获得 swiftVersion
中 endIndex
的偏移量。 但对 Swift 5 中使用的 UTF-8 字符串编码不起作用,因此 Swift 5用 utf16Offset(in:)
替换encodedOffset
来处理这两种情况 [ SE-0241 ]:
let swiftVersion = "Swift 5"
let offset = swiftVersion.endIndex.utf16Offset(in: swiftVersion)
新的指针方法
Swift 5 为 Sequence
增加了 withContiguousStorageIfAvailable(_:)
,以及为MutableCollection
增加了 withContiguousMutableStorageIfAvailable(_:)
,以便为协议扩展中的 withUnsafeBufferPointer(_:)
和 withUnsafeMutableBufferPointer(_:)
提供通用实现[ SE-0237 ]。
SIMD 向量
Swift 5 将处理器的 SIMD 类型操作添加到标准库中,为 SIMD 向量和矩阵提供底层支持。 该特性也简化了<simd/simd.h>
中的 Objective-C、C 和 C++实现。
SIMD,即 Single Instruction, Multiple Data,一条指令操作多个数据.是CPU基本指令集的扩展,用于并行处理数据以提高执行性能。
后记
Swift 5 向 Swift 4.2添加了许多很酷的功能,并使语言ABI稳定,这是该语言发展过程中的一个重要里程碑,因为从现在起语言的变化将会减少。
你可以在官方 Swift 更改日志或 Swift 标准库差异中阅读更多有关此版本 Swift 更新的更多信息。
你还可以查看 Swift 发展提案,了解下一个 Swift 版本中会发生什么变化。 在这里,你可以针对当前正在审核的提案提供反馈,甚至还可以自行提交提案!
网友评论