ハイライト
Swift 6.3 と 6.4 は、日常的なコーディング体験の改善(
anyAppleOSavailability 注釈、module selector、@diagnoseによる警告制御)、標準ライブラリの拡張(task cancellation shield、Dictionary.mapKeyedValues、FilePath)、Swift Testing と XCTest の双方向相互運用、クロスプラットフォーム相互運用(@Cによる C への export、Swift-Java、Wasm)、ownership system の大幅な拡張(Iterableprotocol、borrow/mutateaccessor、Ref/MutableRef)をもたらし、安全性を犠牲にせず高性能コードを書きやすくします。
主要内容
日常コーディングの「小さな面倒」が消えていく
Swift を書いていると、細かなところで余分な入力が必要になることがありました。たとえば some や any を 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 を強制し、finalclass 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)では、一つの要素を変更するだけでも構造全体が二回コピーされます。新しい borrow と mutate 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
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)は prefix&を使い、write access から参照を作ります- 繰り返し行う dictionary lookup をループ外へ持ち出せます
Refは non-escapable なので、コンパイラは変数が scope を離れた時点でアクセスが終わると分かります- 以前必要だった、
inoutparameter と nested function を組み合わせる分かりにくい技巧を避けられます
その他の新しい型:
UniqueArray:non-copyable な dynamic array で、参照カウントの overhead がありませんwithTemporaryAllocation:UnsafeMutableBufferPointerの代わりにOutputSpanを使いますContinuation:resumeが一度だけ呼ばれることを 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/set を borrow/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