Highlight
Swift 6.3 和 6.4 带来了日常编码体验改进(anyAppleOS 可用性注解、模块选择器、@diagnose 警告控制)、标准库增强(任务取消屏蔽、Dictionary.mapKeyedValues、FilePath)、Swift Testing 与 XCTest 双向互操作、跨平台互操作(@C 属性导出到 C、Swift-Java、Wasm)、以及所有权系统的重大扩展(Iterable 协议、borrow/mutate accessor、Ref/MutableRef 类型),让高性能代码在不牺牲安全性的前提下更易编写。
核心内容
日常编码的”小烦恼”消失了
写 Swift 时,一些细节一直让人多敲几下键盘。比如 some 或 any 搭配可选类型时,必须加括号:(any Protocol)?。Swift 6.4 取消了这条规则,编译器会自动理解你的意图。(00:52)
并发任务里抛出的错误如果被静默忽略,以前没有任何提示。现在编译器会发出警告,提醒你要么在任务内部 catch,要么保存任务稍后检查错误。(01:12)defer 块里调用异步函数的旧限制也被解除了。(01:22)
Sendable 检查也有改进。以前一个类因为有 weak var 而不得不用 @unchecked Sendable,现在可以把 weak var 改成 weak let, immutability 让它通过 Sendable 检查。(01:27)如果某个类型明确不应该支持 Sendable,可以用新的 ~Sendable 语法显式声明,而且子类仍可以独立声明 Sendable 合规性。(01:38)
告别冗长的平台可用性注解
Apple 生态从两个平台扩展到五个,可用性注解也跟着膨胀。以前写一个新 API 要列五个平台:
@available(macOS 27, iOS 27, watchOS 27, tvOS 27, visionOS 27, *)
func showStatus() { ... }
Swift 6.4 引入了 anyAppleOS,一行搞定:(02:17)
@available(anyAppleOS 27, *)
func showStatus() { ... }
@available(anyAppleOS 27, *)
@available(tvOS, unavailable)
func launch() { ... }
#if os(anyAppleOS)
func makeLiveActivityWidget() -> some Widget { ... }
#endif
如果有例外平台,先用 anyAppleOS 建立默认值,再用具体平台属性覆盖。#if os(anyAppleOS) 也支持条件编译。
精确控制编译器警告
API 演进中,旧 API 被标记为 deprecated,但有时候你无法立即迁移。@diagnose 属性让你对特定声明中的警告进行精细控制:(02:40)
@diagnose(DeprecatedDeclaration, as: ignored, reason: "Flying with surplus hardware")
func makeApolloSoyuzMission() -> Mission { ... }
@diagnose(StrictMemorySafety, as: warning)
func uplinkCommand(from receiver: inout Receiver, to computer: inout Computer) { ... }
@diagnose(ErrorInFutureSwiftVersion, as: error)
func fetchPosition() -> (x: Double, y: Double, z: Double) { ... }
关键点:
as: ignored关闭特定警告,不影响项目其他部分as: warning开启默认关闭的警告组,如StrictMemorySafetyas: error把未来版本将变成错误的警告提前升级为错误
解决模块命名冲突
两个模块导入了同名类型时,ModuleName.TypeName 的语法在模块本身也有同名类型时会失效——Swift 优先解析为类型名而非模块名。Swift 6.3 引入模块选择器 ::,左侧永远是模块名:(03:47)
import Rocket
import GiftShopToys
let rocket1 = SaturnV() // 歧义错误
let rocket2 = Rocket.SaturnV() // 如果 Rocket 模块里也有 Rocket 类型,会解析错
let rocket3 = Rocket::SaturnV() // 正确指向 Rocket 模块里的 SaturnV
模块选择器也适用于方法名冲突:(05:00)
launchPadTechnician.HumanResources::fire()
推荐在宏展开和自动生成的代码中防御性使用,因为你不清楚目标项目导入了什么。
详细内容
标准库新工具
任务取消屏蔽
检查任务取消状态很重要,但有些操作即使任务被取消也必须完成——比如把数据写完磁盘避免文件损坏。withTaskCancellationShield 让屏蔽区域内的取消检查始终返回 false:(06:40)
extension EmergencyTransponder {
func sendSOS() {
withTaskCancellationShield {
radio.send(makeSOSPacket())
}
}
}
关键点:
- 屏蔽区域内
Task.isCancelled始终为false - 保持区域短小,专注于完成或回滚已启动的工作
Dictionary.mapKeyedValues
mapValues 只传入旧值,如果需要 key 来计算新值,以前只能手动构造字典。现在用 mapKeyedValues,closure 同时接收 key 和 value:(07:06)
func makeCalendarDisplayNames(for missions: [Mission: LaunchWindow]) -> [Mission: String] {
missions.mapKeyedValues { mission, launchWindow in
makeDisplayName(for: mission, in: launchWindow)
}
}
FilePath 类型
不同平台对文件路径的表示有微妙差异。Swift 标准库新增了基于 Swift System 的 FilePath 类型,正确处理 macOS 的 named resource:(07:14)
var path: FilePath = "/var/www/static"
path.components.append("WWDC")
print(path.components)
// ["var", "www", "static", "WWDC"]
var path2: FilePath = "/var/www/static/..namedresource/rsrc"
print(path2.components)
// ["var", "www", "static"]
Swift Testing 增强
Issue 严重级别和动态取消
测试可以记录非致命问题,不阻塞 CI:(07:41)
@Test(arguments: allRockets)
func testBurn(rocket: Rocket) throws {
rocket.burn(for: .seconds(150))
let remaining = rocket.propellantKg / rocket.totalPropellantKg
if remaining < 0.10 {
Issue.record(
"\(rocket.name) remaining fuel is below 10% reserve target",
severity: .warning
)
}
#expect(remaining > 0.02, "\(rocket.name) propellant critically low - abort")
}
参数化测试可以动态跳过不适合的参数:(07:52)
@Test(arguments: allRockets)
func testBurn(rocket: Rocket) throws {
if rocket.engineType == .solid {
try Test.cancel("\(rocket.name) has solid fuel")
}
// ...
}
XCTest 双向互操作
Swift 6.4 中,从 Swift Testing 调用 XCTest 断言时,失败会被报告为 test issue 而非直接崩溃。反过来,在 XCTestCase 中调用 #expect 也正常工作:(08:34)
// 在 Swift Testing 中调用 XCTest
func checkedTransmitAndReceive(on radio: Radio,
packet: Packet,
expectedByteCount: Int) throws -> [UInt8] {
try radio.transmit(bytes: packet.data)
let bytes = try radio.receive()
XCTAssertEqual(bytes.count, expectedByteCount)
return bytes
}
@Test
func pingTest() throws {
let radio = Radio()
let bytes = try checkedTransmitAndReceive(on: radio, packet: .ping, expectedByteCount: 8)
#expect(bytes == [0x00, 0x00, 0xf0, 0x37, 0x0f, 0xc7, 0x00, 0x01])
}
// 在 XCTest 中调用 Swift Testing
class RadioTests: XCTestCase {
func testPingPacketTransmission() {
let radio = Radio()
let bytes = try checkedTransmitAndReceive(on: radio,
packet: .ping,
expectedByteCount: 8)
#expect(bytes == [0x00, 0x00, 0xf0, 0x36, 0x0f, 0xc7, 0x00, 0x02])
}
}
默认以 warning 报告,可在 Xcode build settings 中升级为 test failure。
Subprocess 1.0
Subprocess 包正式发布 1.0,API 简化,跨平台支持大幅提升。输出流通过 AsyncBufferSequence 获取,保证每个流只创建一次:(10:01)
let result = try await Subprocess.run(.name("ls"),
input: .none,
output: .sequence,
error: .string(limit: 4096)) { execution in
execution.standardOutput.strings().filter { $0.hasSuffix(".obj") }
}
for try await objectFile in result.closureOutput {
print("Object file: \(objectFile)")
}
关键点:
strings()方法按行读取输出,正确处理 grapheme cluster 边界- 支持跨平台特定的进程文件描述符和终止状态
Foundation: ProgressManager
ProgressManager 是 Foundation 中新的进度报告类型,与 async/await 并发模型配合,分离进度组合和进度报告,支持类型安全的元数据附加:(10:37)
let manager = ProgressManager(totalCount: 100)
try await rocket.launch(mission.subprogress(assigningCount: 100))
// 观察进度
Task {
for await update in Observations({ mission.fractionCompleted }) {
print("Mission \(Int(update * 100))%")
}
}
// 附加元数据
extension Rocket {
func ascend(_ progress: consuming Subprogress) async throws {
let stage = progress.start(totalCount: 3)
stage.deltaV = 3_400; try await burn(); stage.complete(count: 1)
stage.deltaV = 2_100; try await stageSeparation(); stage.complete(count: 1)
stage.deltaV = 1_800; try await coast(); stage.complete(count: 1)
}
}
print("Δv to orbit: \(mission.summary(of: \.deltaV)) m/s")
跨平台互操作
导出 Swift 函数到 C
Swift 6.4 支持用 @C 属性将 Swift 函数暴露给 C,类似 @objc 但面向 C 互操作:(12:39)
@c
@implementation
func launchWindowLength(_ window: Span<LaunchWindow>) -> TimeInterval {
window[0].end.timeIntervalSince(window[0].start)
}
关键点:
@c+@implementation一起使用,实现已在 C header 中声明的函数- Swift Span 自动桥接到 C 数组
- 编译器阻止传入 C 不兼容类型
Swift-Java 互操作
Swift-Java 包现在支持从 Java 调用 Swift 的 async 和 throwing 函数,泛型系统支持约束扩展和 Java 类遵循 Swift 协议。Android 官方 Swift SDK 已可在 swift.org 下载。(15:14)
WebAssembly 支持
Swift 开源工具链已支持编译到 WebAssembly。JavaScriptKit 项目让 Swift 与 JavaScript 的桥接更安全、更快——Goodnotes 团队发现安全桥接比动态桥接快 35-40 倍。(16:40)
Embedded Swift 扩展
Embedded Swift 新增 existential types 支持、untyped throws,调试元数据存入 DWARF 而非二进制本身,大幅减小体积的同时改善 core dump 调试体验。(18:08)
性能调优:控制优化器决策
内联控制
Swift 6.4 新增 @inline(always),与已有的 @inline(never) 配对:(21:30)
@inline(never)
func makeInts(randomized: Bool) -> [256 of Int] { ... }
@inline(always)
func makeInts(randomized: Bool) -> [256 of Int] { ... }
关键点:
@inline(never)禁止内联,适用于代码体积敏感场景@inline(always)强制内联,配合final类方法使用效果更佳- 虚方法调用可能仍无法内联
泛型特化
@specialized 属性让库作者指示编译器为特定类型生成特化版本:(22:17)
@specialized(where Values == [UInt8])
func histogram<Values>(of values: Values) -> [256 of Int] where Values: Sequence<UInt8> {
var result = makeInts(randomized: false)
for value in values {
result[Int(value)] += 1
}
return result
}
关键点:
- 在属性中写 where 子句约束泛型参数
- 编译器为约束条件生成特化版本,消除泛型开销
- 适用于被频繁以特定类型调用的泛型函数
所有权系统扩展
Iterable 协议
Swift 6.4 的 for 循环支持新的 Iterable 协议。与 Sequence 不同,Iterable 通过 borrow 访问元素,不复制数据,也不做引用计数:(26:18)
protocol Iterable<Element, Failure>: ~Copyable, ~Escapable {
associatedtype Element: ~Copyable
associatedtype IterableIterator: IterableIteratorProtocol<Element, Failure>, ~Copyable, ~Escapable
associatedtype Failure: Error = Never
func makeIterableIterator() -> IterableIterator
var underestimatedCount: Int { get }
}
关键点:
for循环优先使用Sequence,回退到Iterable- 支持 non-copyable 元素
- 支持 typed throws
- 迭代期间禁止修改 Iterable(由独占性检查保证)
- 元素分批通过
Span返回,提升效率
borrow 和 mutate accessor
get/set accessor 通过复制数据工作。对于大值类型(如 256 个 Int 的 InlineArray,约 2KB),修改一个元素要复制整个结构两次。新的 borrow 和 mutate accessor 直接操作原始存储:(28:19)
@safe public struct UniqueBox<Value: ~Copyable>: ~Copyable {
private let valuePointer: UnsafeMutablePointer<Value>
public init(_ value: consuming Value) { ... }
public var value: Value {
borrow { valuePointer.pointee }
mutate { &valuePointer.pointee }
}
}
关键点:
borrow提供只读共享访问,不复制mutate提供独占修改访问,就地修改- 支持 non-copyable 值类型
UniqueBox已加入标准库
Ref 和 MutableRef
Ref 类似 Span 但针对单个值,可以存储在变量中、传入函数、从函数返回:(29:42)
func updateCount<Key: Hashable>(
for key: Key,
from sets: [Set<Key>],
in counts: inout [Key: Int]
) {
var countRef = MutableRef(&counts[key, default: 0])
for set in sets {
if set.contains(key) {
countRef.value += 1
}
}
}
关键点:
MutableRef(&expression)用前缀&从写访问创建- 把重复的字典查找提升到循环外
- Ref 是 non-escapable 的,编译器知道访问在变量离开作用域时结束
- 避免以前需要用
inout参数配合嵌套函数的晦涩技巧
其他新类型:
UniqueArray:non-copyable 的动态数组,无引用计数开销withTemporaryAllocation:用OutputSpan替代UnsafeMutableBufferPointerContinuation:编译时检查只 resume 一次,兼具CheckedContinuation的安全和UnsafeContinuation的效率
核心启发
1. 用 anyAppleOS 简化多平台代码
做什么:把项目中所有五平台并列的 @available 注解和 #if os 条件编译替换为 anyAppleOS。
为什么值得做:减少样板代码,降低新增平台时的维护成本。
怎么开始:搜索项目中的 @available(macOS 和 #if os(macOS) || os(iOS) 模式,批量替换。
2. 用 @diagnose 渐进式处理技术债务
做什么:对暂时无法迁移的 deprecated API 调用点加上 @diagnose(DeprecatedDeclaration, as: ignored),而不是全局关闭警告。
为什么值得做:精确控制警告范围,避免遗漏真正需要关注的 deprecated 调用。
怎么开始:在 Xcode 的 Issue Navigator 中筛选 deprecated 警告,逐个评估是否添加 @diagnose。
3. 用 Iterable 优化大数据遍历
做什么:为自定义集合类型实现 Iterable 协议,替代 Sequence。
为什么值得做:避免遍历大集合时的复制和引用计数开销,支持 non-copyable 元素。
怎么开始:定义 makeIterableIterator() 返回的迭代器,实现 nextSpan(maximumCount:) 分批返回 Span<Element>。
4. 用 borrow/mutate accessor 优化大值类型属性
做什么:把包装大值类型的容器(如缓存、配置存储)的 get/set 改为 borrow/mutate。
为什么值得做:消除大值类型属性访问时的隐式复制,直接操作底层存储。
怎么开始:在自定义类型的 computed property 中替换 accessor 关键字,配合 UniqueBox 或自定义指针管理。
5. 用 MutableRef 消除重复字典查找
做什么:在循环中多次修改同一个字典键的场景,用 MutableRef 把查找提升到循环外。
为什么值得做:减少 O(n) 次哈希查找,提升热点代码性能。
怎么开始:找到循环内重复访问 dict[key] 的代码,提取 var ref = MutableRef(&dict[key, default: 0]),循环内改用 ref.value。
关联 Session
- 267 - Swift Testing — 深入讲解 Swift Testing 的迁移策略和高级用法
- 269 - SwiftUI — SwiftUI 新特性与 Swift 语言改进的配合使用
- 328 - MLX Swift — 高性能计算场景下 Swift 所有权系统的实际应用
- 258 - Xcode 27 — Xcode 27 新功能与 Swift 开发工作流改进
- 289 - AppKit modernization — AppKit 现代化中 Swift 新语言特性的应用
评论
GitHub Issues · utterances