WWDC Quick Look 💓 By SwiftGGTeam
Swift の新機能

Swift の新機能

元の動画を見る

ハイライト

Swift 6.3 と 6.4 は、日常的なコーディング体験の改善(anyAppleOS availability 注釈、module selector、@diagnose による警告制御)、標準ライブラリの拡張(task cancellation shield、Dictionary.mapKeyedValuesFilePath)、Swift Testing と XCTest の双方向相互運用、クロスプラットフォーム相互運用(@C による C への export、Swift-Java、Wasm)、ownership system の大幅な拡張(Iterable protocol、borrow/mutate accessor、Ref/MutableRef)をもたらし、安全性を犠牲にせず高性能コードを書きやすくします。

主要内容

日常コーディングの「小さな面倒」が消えていく

Swift を書いていると、細かなところで余分な入力が必要になることがありました。たとえば someany を optional 型と組み合わせる場合、(any Protocol)? のように括弧が必要でした。Swift 6.4 はこのルールをなくし、コンパイラが意図を自動で理解します。(00:52)

concurrent task から throw された error が静かに無視されても、以前は何の通知もありませんでした。現在のコンパイラは warning を出し、task 内で catch するか、task を保存して後で error を確認するよう促します。(01:12) defer ブロック内で async 関数を呼ぶ旧来の制限も解除されました。(01:22)

Sendable チェックも改善されています。以前は weak var を持つために @unchecked Sendable が必要だった class は、今なら weak let に変えることで、immutability により Sendable チェックを通過できます。(01:27) ある型が明確に Sendable をサポートすべきでない場合は、新しい ~Sendable 構文で明示できます。さらに subclass は独立して Sendable 準拠を宣言できます。(01:38)

長い platform availability 注釈に別れを告げる

Apple エコシステムは二つの platform から五つへ広がり、availability 注釈も膨らみました。以前は新しい API に五つの platform を列挙する必要がありました。

@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

例外となる platform がある場合は、まず anyAppleOS でデフォルト値を作り、具体的な platform 属性で上書きします。#if os(anyAppleOS) も条件コンパイルで使えます。

コンパイラ warning を精密に制御する

API が進化する過程で、古い API は deprecated としてマークされます。しかし、すぐに移行できない場合もあります。@diagnose 属性は、特定の宣言における warning を細かく制御できます。(02:40)

@diagnose(DeprecatedDeclaration, as: ignored, reason: "余剰ハードウェアで飛行するため")
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 は特定の warning を無効化し、プロジェクトの他の部分には影響しません
  • as: warning は、StrictMemorySafety のようにデフォルトで無効な warning group を有効にします
  • as: error は、将来の Swift 版で error になる warning を、今の段階で error に昇格します

module 名の衝突を解決する

二つの module が同名の型を import しているとき、ModuleName.TypeName 構文は、module 自身にも同名の型がある場合に失敗することがあります。Swift は module 名ではなく型名として先に解釈するためです。Swift 6.3 は module selector :: を導入し、左側を常に module 名として扱います。(03:47)

import Rocket
import GiftShopToys

let rocket1 = SaturnV()            // あいまいさによる error
let rocket2 = Rocket.SaturnV()     // Rocket module 内にも Rocket 型がある場合、誤って解釈される可能性がある
let rocket3 = Rocket::SaturnV()    // Rocket module 内の SaturnV を正しく指す

module selector はメソッド名の衝突にも使えます。(05:00)

launchPadTechnician.HumanResources::fire()

macro 展開や自動生成コードでは、防御的に使うことをおすすめします。対象プロジェクトが何を import しているか分からないためです。

詳細

標準ライブラリの新しい道具

task cancellation shield

task の cancellation 状態を確認することは重要ですが、task が cancel されても必ず完了すべき操作があります。たとえばファイル破損を避けるため、ディスク書き込みを最後まで終える場合です。withTaskCancellationShield は、shield された領域内の cancellation チェックを常に false にします。(06:40)

extension EmergencyTransponder {
    func sendSOS() {
        withTaskCancellationShield {
            radio.send(makeSOSPacket())
        }
    }
}

重要な点:

  • shield された領域では Task.isCancelled が常に false になります
  • 領域は短く保ち、開始済みの作業を完了または rollback することに集中します

Dictionary.mapKeyedValues

mapValues は古い値だけを渡します。新しい値の計算に key が必要な場合、以前は手動で dictionary を構築するしかありませんでした。今は 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 型

platform ごとにファイルパスの表現には微妙な違いがあります。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 の重要度と動的 cancellation

テストは非致命的な問題を記録でき、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) の残燃料が 10% の予備目標を下回っています",
            severity: .warning
        )
    }

    #expect(remaining > 0.02, "\(rocket.name) の推進剤が危険なほど少ないため中止します")
}

パラメータ化テストは、適さないパラメータを動的にスキップできます。(07:52)

@Test(arguments: allRockets)
func testBurn(rocket: Rocket) throws {
    if rocket.engineType == .solid {
        try Test.cancel("\(rocket.name) は固体燃料です")
    }
    // ...
}

XCTest の双方向相互運用

Swift 6.4 では、Swift Testing から XCTest assertion を呼んだとき、失敗は直接クラッシュせず 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 package は正式に 1.0 になりました。API は簡潔になり、クロスプラットフォーム対応も大きく向上しています。出力 stream は AsyncBufferSequence から取得し、各 stream が一度だけ作成されることを保証します。(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("オブジェクトファイル: \(objectFile)")
}

重要な点:

  • strings() メソッドは出力を行単位で読み取り、grapheme cluster の境界を正しく扱います
  • platform 固有の process file descriptor と終了状態をサポートします

Foundation: ProgressManager

ProgressManager は Foundation の新しい進捗報告型で、async/await の concurrency model と組み合わせて使います。進捗の合成と進捗報告を分離し、型安全な metadata 付加をサポートします。(10:37)

let manager = ProgressManager(totalCount: 100)
try await rocket.launch(mission.subprogress(assigningCount: 100))

// 進捗を監視する
Task {
    for await update in Observations({ mission.fractionCompleted }) {
        print("ミッション \(Int(update * 100))%")
    }
}

// metadata を付加する
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: \(mission.summary(of: \.deltaV)) m/s")

クロスプラットフォーム相互運用

Swift 関数を C へ export する

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 array へ自動 bridge されます
  • コンパイラは C と互換性のない型の受け渡しを防ぎます

Swift-Java 相互運用

Swift-Java package は、Java から Swift の async 関数と throwing 関数を呼べるようになりました。generic system は constrained extension と、Java class が Swift protocol に準拠することをサポートします。Android 向けの公式 Swift SDK は swift.org からダウンロードできます。(15:14)

WebAssembly 対応

Swift の open-source toolchain は WebAssembly へのコンパイルに対応しました。JavaScriptKit project は Swift と JavaScript の bridge をより安全かつ高速にします。Goodnotes チームは、安全な bridge が dynamic bridge より 35-40 倍速いことを確認しました。(16:40)

Embedded Swift の拡張

Embedded Swift は existential types と untyped throws に対応しました。debugging metadata は binary 本体ではなく DWARF に保存され、サイズを大幅に減らしながら core dump デバッグ体験を改善します。(18:08)

パフォーマンス調整:optimizer の判断を制御する

inline 制御

Swift 6.4 は既存の @inline(never) と対になる @inline(always) を追加しました。(21:30)

@inline(never)
func makeInts(randomized: Bool) -> [256 of Int] { ... }

@inline(always)
func makeInts(randomized: Bool) -> [256 of Int] { ... }

重要な点:

  • @inline(never) は inline を禁止し、コードサイズが重要な場面に向きます
  • @inline(always) は inline を強制し、final class method と組み合わせると効果的です
  • virtual method call はそれでも inline できない場合があります

generic specialization

@specialized 属性により、library author は特定の型向けに 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 句を書き、generic parameter を制約します
  • コンパイラは制約に合う specialized 版を生成し、generic overhead を取り除きます
  • 特定の型で頻繁に呼ばれる generic 関数に向いています

ownership system の拡張

Iterable protocol

Swift 6.4 の for ループは新しい Iterable protocol をサポートします。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 へ fallback します
  • non-copyable 要素をサポートします
  • typed throws をサポートします
  • iteration 中に Iterable を変更することは禁止され、exclusivity check によって保証されます
  • 要素は Span 経由で batch として返され、効率が上がります

borrow と mutate accessor

get/set accessor はデータのコピーによって動作します。256 個の Int を持つ InlineArray のような大きな値型(約 2 KB)では、一つの要素を変更するだけでも構造全体が二回コピーされます。新しい borrowmutate accessor は元の storage を直接操作します。(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 value type をサポートします
  • UniqueBox は標準ライブラリに追加されています

Ref と MutableRef

RefSpan に似ていますが、単一の値を対象にします。変数に保存したり、関数へ渡したり、関数から返したりできます。(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) は prefix & を使い、write access から参照を作ります
  • 繰り返し行う dictionary lookup をループ外へ持ち出せます
  • Ref は non-escapable なので、コンパイラは変数が scope を離れた時点でアクセスが終わると分かります
  • 以前必要だった、inout parameter と nested function を組み合わせる分かりにくい技巧を避けられます

その他の新しい型:

  • UniqueArray:non-copyable な dynamic array で、参照カウントの overhead がありません
  • withTemporaryAllocationUnsafeMutableBufferPointer の代わりに OutputSpan を使います
  • Continuationresume が一度だけ呼ばれることを compile time にチェックし、CheckedContinuation の安全性と UnsafeContinuation の効率を兼ね備えます

重要な示唆

1. anyAppleOS でマルチプラットフォームコードを簡潔にする

何をするか:プロジェクト内にある五 platform 並びの @available 注釈と #if os 条件コンパイルを anyAppleOS に置き換えます。

なぜ価値があるか:boilerplate を減らし、新しい platform が増えたときの保守コストを下げます。

どう始めるか:プロジェクト内の @available(macOS#if os(macOS) || os(iOS) のような pattern を検索し、まとめて置き換えます。

2. @diagnose で技術的負債を段階的に扱う

何をするか:すぐには移行できない deprecated API の呼び出し箇所に @diagnose(DeprecatedDeclaration, as: ignored) を付け、warning を global に無効化しないようにします。

なぜ価値があるか:warning の範囲を正確に制御でき、本当に注意すべき deprecated 呼び出しを見落としにくくなります。

どう始めるか:Xcode の Issue Navigator で deprecated warning を絞り込み、各箇所に @diagnose を追加すべきか一つずつ評価します。

3. Iterable で大規模データ走査を最適化する

何をするか:カスタム collection 型に Sequence の代わりに Iterable protocol を実装します。

なぜ価値があるか:大きな collection を走査するときのコピーと参照カウント overhead を避け、non-copyable 要素にも対応できます。

どう始めるか:makeIterableIterator() が返す iterator を定義し、nextSpan(maximumCount:)Span<Element> を batch として返すように実装します。

4. borrow/mutate accessor で大きな値型 property を最適化する

何をするか:cache や設定 storage のように大きな値型を包む container の get/setborrow/mutate へ変えます。

なぜ価値があるか:大きな値型 property にアクセスするときの暗黙のコピーをなくし、底層 storage を直接操作できます。

どう始めるか:カスタム型の computed property で accessor keyword を置き換え、UniqueBox または自前の pointer 管理と組み合わせます。

5. MutableRef で繰り返し dictionary lookup をなくす

何をするか:ループ内で同じ dictionary key を何度も変更する場面では、MutableRef を使って lookup をループ外へ持ち出します。

なぜ価値があるか:O(n) 回の hash lookup を減らし、ホットパスの性能を上げます。

どう始めるか:ループ内で dict[key] を繰り返しアクセスしているコードを見つけ、var ref = MutableRef(&dict[key, default: 0]) を取り出し、ループ内では ref.value を使います。

関連セッション

  • 267 - Swift Testing — Swift Testing の移行戦略と高度な使い方を詳しく解説
  • 269 - SwiftUI — SwiftUI の新機能と Swift 言語改善の組み合わせ
  • 328 - MLX Swift — 高性能計算における Swift の ownership system の実践的な活用
  • 258 - Xcode 27 — Xcode 27 の新機能と Swift 開発ワークフローの改善
  • 289 - AppKit modernization — AppKit modernization における Swift の新しい言語機能の活用

コメント

GitHub Issues · utterances