WWDC Quick Look 💓 By SwiftGGTeam
What's new in Swift

What's new in Swift

观看原视频

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 时,一些细节一直让人多敲几下键盘。比如 someany 搭配可选类型时,必须加括号:(any Protocol)?。Swift 6.4 取消了这条规则,编译器会自动理解你的意图。(00:52

并发任务里抛出的错误如果被静默忽略,以前没有任何提示。现在编译器会发出警告,提醒你要么在任务内部 catch,要么保存任务稍后检查错误。(01:12defer 块里调用异步函数的旧限制也被解除了。(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 开启默认关闭的警告组,如 StrictMemorySafety
  • as: 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),修改一个元素要复制整个结构两次。新的 borrowmutate 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 替代 UnsafeMutableBufferPointer
  • Continuation:编译时检查只 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

评论

GitHub Issues · utterances