这个 session 介绍了 swift 相关的新特性。
相关文档:
Swift Packages 更新
Trust on first use (TOFU)
- 新的安全协议,其中在首次下载包时记录包的指纹
- 后续下载将验证此指纹,如果指纹不同,则报告错误
插件
Xcode 插件又回归了,记得 Xcode8 之前有 IDE 插件,很多丰富好用的插件,后来苹果更新了新版 Xcode 之后,插件被取消了,现在终于又可以在 Xcode 中使用。新版本提供有两种类型的插件,一种是命令插件,可以修改代码,用于注释自动生成,格式调整等,另一种是 Build Plugin,在构建时注入额外步骤,比如源代码生成或者资源处理。更详细的内容,可以看这个 session。 Meet Swift Package plugins 和 Create Swift Package plugins。 插件被声明在 package 下一个名为 plugins 文件夹中,插件被视为 swift 可执行文件。
命名冲突
当两个独立的包具有同名模块时,会产生命名冲突,现在 swift 允许在包外使用 moduleAliases 字段重命名,这样可以解决模块重名的问题。
let package = Package(
name: "MyStunningApp",
dependencies: [
.package(url: "https://.../swift-metrics.git"),
.package(url: "https://.../swift-log.git") // ⚠️ swift-log and swift-metric define a Logging module
],
targets: [
.executableTarget(
name: "MyStunningApp",
dependencies: [
.product(name: "Logging", package: "swift-log"),
.product(name: "Metrics", package: "swift-metrics",
moduleAliases: ["Logging": "MetricsLogging"]), // 👈🏻 module aliasing
])])
Swift 底层性能优化
- 编译时间提升
- 更快的泛型类型检查
- 运行时改进
- Swift5.7 会对协议一致性检查进行缓存,提高编译速度。
- Swift driver(Swift 编译器驱动程序)
- Xcode 现在已经将 Swift driver 集成到构建系统内部,而不是一个独立的可执行文件,提高了项目构建速度。
Swift 并发模型
安全检查
Swift 5.5 引入了新的并发模型,Swift 5.7 主要针对数据竞争安全对并发模型进行了完善。Swift 并发模型支持 macOS 10.15、iOS 13、tvOS 13 和 watchOS 6 以上系统。 开发者现在可以通过在 Build Settings 中设置 Strict Concurrency Checking 来体验安全检查。在 swift 6 中,如果开发者在两个线程中都修改了同一个值,并且没有使用 actor,应该出现报错。
distributed
Actors
关键字 distributed 可以用来修饰 actor 和 actor 的方法,表明 actor 可能部署在远端机器上。
distributed actor Player {
var ai: PlayerBotAI?
var gameState: GameState
distributed func makeMove() -> GameMove {
return ai.decideNextMove(given: &gameState)
}
}
分布式的 actor 方法执行可能会由于网络原因失败,所以在外部调用 actor 方法时需要在 await 前面加上关键字 try。如果想要了解更多可以参考 Meet distributed actors in Swift。
其他优化
Async Algorithms Package 苹果开源了用于处理 AsyncSequence 的算法,使跨平台部署更加灵活。 Actor 会执行优先级最高的任务,同时并发模型内置了防止优先级反转的机制,来确保低优先级任务不会阻塞高优先级任务。 Instruments 中新增了工具 Swift Concurrency,帮助开发者排查性能问题。提供了一整套工具来帮助可视化和优化并发代码。可以参考 Visualize and optimize Swift concurrency。
Swift 语言优化
If let 简写优化
之前我们是这样写的:
if let name = name {
print("Hello, \\(name)!")
}
if let unwrappedName = name {
print("Hello, \\(unwrappedName)!")
}
现在我们可以这样写:
var name: String? = "Linda"
if let name {
print("Hello, \\(name)!")
}
但是这种写法不能扩展到对象的属性,也就是说不能这样写:
struct User {
var name: String
}
let user: User? = User(name: "Linda")
if let user.name {
print("Welcome, \\(user.name)!")
}
返回类型推断
在 swift 5.7 之前,我们需要在闭包中显式的指定返回类型,比如:
let oldResults = scores.map { score -> String in
if score >= 85 {
return "\\(score)%: Pass"
} else {
return "\\(score)%: Fail"
}
}
但现在,swift 可以自动推断类型,我们可以省略返回类型:
let scores = [100, 80, 85]
let results = scores.map { score in
if score >= 85 {
return "\\(score)%: Pass"
} else {
return "\\(score)%: Fail"
}
}
允许指针类型转换
针对从外部引入的方法和函数,Swift 支持在 C 语言中合法的指针类型转换,以下情况将不会再有报错。
正则表达式
Swift 5.7 引入了和正则表达式相关的大量改进,极大的改进了我们处理字符串的方式。 我们从易到难来使用一下,首先新增了一些字符串方法, 例如:
let message = "the cat sat on the mat"
print(message.ranges(of: "at"))
print(message.replacing("cat", with: "dog"))
print(message.trimmingPrefix("the "))
一样看去好像没什么区别,他们的优势在于接受正则表达式:
print(message.ranges(of: /[a-z]at/))
print(message.replacing(/[a-m]at/, with: "dog"))
print(message.trimmingPrefix(/The/.ignoresCase()))
除了支持正则表达式的字符串,swift 还提供了一个专用的正则表达式类型,就像这样工作:
do {
let atSearch = try Regex("[a-z]at")
print(message.ranges(of: atSearch))
} catch {
print("Failed to create regex")
}
但这里有一个地方需要注意,我们使用字符串初始化 Regex 类型,swift 会在运行时解析这个字符串,不能在编译时替我们检查正则表达式是否有效,但是我们使用像 message.ranges(of: /[a-z]at/)
这种方式时,是在编译时检查,这种方式相对更安全一些。
除此以外,swift 更是给我们提供了一种 DSL 语法让我们能够初始化 Regex,例如:
let search3 = Regex {
"My name is "
Capture {
OneOrMore(.word)
}
" and I'm "
Capture {
OneOrMore(.digit)
}
" years old."
}
并且如果我们使用 TryCapture 替代 Capture,swift 会自动根据是否匹配而捕获失败或者抛出错误。
let search4 = Regex {
"My name is "
Capture {
OneOrMore(.word)
}
" and I'm "
TryCapture {
OneOrMore(.digit)
} transform: { match in
Int(match)
}
Capture(.digit)
" years old."
}
我们甚至可以使用特定类型和已经命名的匹配规则绑定在一起,比如:
let nameRef = Reference(Substring.self)
let ageRef = Reference(Int.self)
let search5 = Regex {
"My name is "
Capture(as: nameRef) {
OneOrMore(.word)
}
" and I'm "
TryCapture(as: ageRef) {
OneOrMore(.digit)
} transform: { match in
Int(match)
}
Capture(.digit)
" years old."
}
if let result = greeting.firstMatch(of: search5) {
print("Name: \\(result[nameRef])")
print("Age: \\(result[ageRef])")
}
以上就是新的正则匹配的语法,我个人偏向使用 regex 字面量的形式,在支持 swift6 的 Xcode 中,在 Other Swift Flags
中添加“-Xfrontend -enable-bare-slash-regex”来启用这种语法。
基于默认表达式的类型推断
在 Swift 5.7 之前,不支持为泛型参数提供默认值,因为在现有语法规则下,默认值的类型必须在任何调用场景都有效才行。Swift 5.7 提供了一种基于默认表达式的类型推断方式,使得可以为泛型参数添加默认值。如果想要了解更多可以参考 Type inference from default expressions。
func compute<C: Collection>(_ values: C = [0, 1, 2]) {}
不透明的参数声明
在 swift5.7 中,我们可以在一些使用简单泛型的地方使用一些参数声明。 比如下面这个例子,我们想要编写一个检查数组是否排序的函数,可以这样写:
func isSorted(array: [some Comparable]) -> Bool {
array == array.sorted()
}
这段函数和下面这段函数等价,是一种新的语法糖:
func isSortedOld<T: Comparable>(array: [T]) -> Bool {
array == array.sorted()
}
返回不透明的泛型
现在我们可以一次返回多个不透明类型:
func showUserDetails() -> (some Equatable, some Equatable) {
(Text("Username"), Text("@twostraws"))
}
还可以返回不透明类型数组:
func createUser() -> [some View] {
let usernames = ["@frankefoster", "@mikaela__caron", "@museumshuffle"]
return usernames.map(Text.init)
}
或者调用一个函数,这个函数在调用的时候返回一个不透明类型:
func createDiceRoll() -> () -> some View {
return {
let diceRoll = Int.random(in: 1...6)
return Text(String(diceRoll))
}
}
总结
以上就是我比较关注的 swift5.7 的一些新特性,当然并不完整,除了以上这些,还可以在 Swift Evolution 页面搜索 5.7 查看 Swift 5.7 相关变更。
其他
时间标准
Swift 5.7 引入了一种新的标准方式来获取和表示时间,可分为以下三个部分: 1.Clock: 表示当下,并且能提供在将来特定时间点唤起的功能 2.Instant: 表示某个瞬间 3.Duration: 用于计量流逝的时间 Clock 有 ContinuousClock 和 SuspendingClock 两种内置时钟,ContinuousClock 在系统休眠时也会保持时间递增,而 SuspendingClock 则不会。Task 休眠相关的 API 也会根据新标准有所更新。如果想要了解更多可以参考 Clock, Instant, and Duration。
extension Task {
@available(*, deprecated, renamed: "Task.sleep(for:)")
public static func sleep(_ duration: UInt64) async
@available(*, deprecated, renamed: "Task.sleep(for:)")
public static func sleep(nanoseconds duration: UInt64) async throws
public static func sleep(for: Duration) async throws
public static func sleep<C: Clock>(until deadline: C.Instant, tolerance: C.Instant.Duration? = nil, clock: C) async throws
}
基于默认表达式的类型推断
在 Swift 5.7 之前,不支持为泛型参数提供默认值,因为在现有语法规则下,默认值的类型必须在任何调用场景都有效才行。Swift 5.7 提供了一种基于默认表达式的类型推断方式,使得可以为泛型参数添加默认值。如果想要了解更多可以参考 Type inference from default expressions。
func compute<C: Collection>(_ values: C = [0, 1, 2]) {
}