Swift6・Strict Concurrency移行で見えた“想定外の作業”と反省

DX本部システム開発第二事業部の冨永です。

現在、主にiOS/iPadアプリの開発を担当しております。

今回は、Swift 6・Strict Concurrency移行対応と想定外にかかった作業について書きたいと思います。 今後、Swift 6・Strict Concurrency移行を検討されている方がいらっしゃれば、参考になればと思います。

目次

Strict Concurrency対応の背景

Strict Concurrencyとは?ざっくり解説

Swift には ClassStruct と並んで Actor という型があります。Actor非同期処理中のデータを守るための“金庫” のようなもので、同時にアクセスできるタスクを 1 つに絞る ことでデータ競合(Data Race)を防ぎます。

比較対象 従来 (スレッド + ロック) Swift の Actor
保護単位 mutex / DispatchQueue などを 呼び出し側 が管理 型そのもの が排他制御を内包
ミス時の結果 ロック漏れ・デッドロック・クラッシュ コンパイラまたはランタイムが検知しやすい
記述量 明示的な lock/unlock が散在 Actor キーワード + await で完結
actor Counter {
    private var value = 0
    func increment() {
        value += 1 // 同時アクセスでも Actor が守られているため、非同期で同時実行時にvalueが0→1→1となることはない。

    }
    func current() -> Int { value }
}

Swift 5 までの課題

ただし、Swift5までのActorでは完全にデータ競合を防ぐことはできませんでした。 ざっくり説明しますと、Actor を使っても Actor 間で同じオブジェクトを同時編集できる抜け道 が残っていたため、Actor間のオブジェクトがデータ競合の可能性がありました。

Swift5までのConcurrencyのざっくりとした図

Swift 6 + Strict Concurrency の解決策

そこで新たに登場したSwift6のStrict Concurrencyモードでは従来使っていたConcurrencyをさらに厳密にコンパイル時にチェックし、 Actor間のオブジェクトはSendableに準拠する必要があるなど、対応が必要になりました。

  • Swift6ではComplete Concurrency Checking がデフォルトで有効 ➡️ Sendable でない型を跨いで渡すと コンパイルエラー
  • @MainActor / @GlobalActor などのアノテーション不足も検出。
  • 共有が必要なデータは 値セマンティクス (Copy‑on‑Write) を持つ Sendable 型へ集約し、競合を根本から排除。

Swift6のStrict Concurrency対応のざっくりとした図

Sendable とは?
データ競合しない型であることを保証するプロトコルになります。個人的には“値セマンティクス(コピー後に相互に影響しない)” を満たす型であることを示すプロトコルかなと解釈しました。struct は値型なので自動適合しやすく、class はイミュータブル(不変、そして継承不可)にするか、 @unchecked Sendable を付けてコンパイルチェックの対象から外す必要があります。

補足

Strict Concurrency チェック自体は Swift 5 でも有効化できました。ただし、警告止まりです。

  • actor は Swift 5.5 で導入
  • Swift 5.9 でも Strict Concurrency は opt-in 可能
  • Swift 6 モードで警告がすべてエラー化

そのため、

  • Swift 5 のまま Strict Concurrency を Complete に警告を潰し切る
  • Swift 6 に切り替える

という段階移行が現実的でした。

Strict Concurrency 対応のメリット

最大のメリットは、再現性の低いクラッシュが減ることです。

観点 メリット
安全性 データ競合・スレッド不整合をコンパイル時に検出しクラッシュを未然に防止
保守性 アノテーションで依存関係が明示され、リファクタ時の影響範囲が可視化
パフォーマンス 競合回避のためのロックが不要になり、ランタイム最適化が容易

作業手順(ざっくり)

想定していた作業手順は以下になります。

  1. Xcode 16 へアップデート
    • CI/CDを使っている場合はCI/CDの設定 も切り替え
  2. 依存ライブラリ更新(通信・DB・開発ツール)
    • 通信形ライブラリやDB系のライブラリ、SwiftFormatなどの開発ツールライブラリを更新
  3. Swift5でStrict Concurrency チェックレベルをCompleteに引き上げ で警告・警告数を確認する(この時点で見積を行うと良いです)
    • 大体、警告の件数に対して1件あたりが0.5h~1hくらいが作業量でした。
  4. 警告解消の実装対応
    • @MainActor/nonisolatedの付与
    • Actor境界を跨ぐオブジェクトのSendable対応 or @unchecked Sendable対応
    • 解決困難な場合は
      • MainActor.assumeIsolatedまたは@preconcurrencyの利用
      • 未対応のライブラリは@preconcurrency import UnmigratedLibrary
      • nonisolated(unsafe)の使用
  5. Swift Language Version を “Swift 6” へ変更

実際に発生した作業量と工数

ある中規模(2〜3人開発)のアプリでの実績は以下の通りです。

  • Xcode 更新:1 人日
  • ライブラリ更新:4.5 人日
  • Strict Concurrency 対応:約 7 人日

QA 期間:約 7 日(工期)

結果、移行だけで約 1 ヶ月かかりました。

※PRレビュー工数・Strict Concurrency調査工数は含まれておりません。


想定外工数の主な要因

要因 工数 (目安) 詳細
SwiftFormatのルール変更 1 人日 Swift 6モードではSwiftFormatのルールが変わったとのことで、差分が大量発生し、対応が必要でした。
Realmのバージョンアップ移行 2.5 人日 公式ドキュメントの情報が古く、調査・試行錯誤に時間を要しました
Sendable未対応ライブラリの回避策検討 0.5 人日 Push通知系で Apple 公式ライブラリが未対応だったため、@preconcurrency で一旦逃がす方針で修正しました。
DIコンテナ再設計 1 人日 継承ベースのオブジェクトが Sendable 制約で使用できないことがわかり、構造を再設計しました。
開発者テスト工数 0.25 人日 広告計測ライブラリ・通知を手動で確認することが必要でした。
PRレビュー増大 1.5 人日 修正量が大きく、またレビュワー側にも知識共有が必要でした。
QAによるSwiftUI挙動差分対応 2 人日~ iOS 18 + Xcode 16 で MapKitの挙動が変わり追加修正が必要でした。

反省ポイント(学び)

  • DependaBotで、常日頃ライブラリのアップデート情報をWatchする。特にSwift6対応情報をキャッチしておけばよかったなと思いました。
  • SwiftFormatなどのフォーマットツール対応を見積に含める。
  • 公式ドキュメントの更新漏れに備える。公式ドキュメントに書いてない。。!?と思っていても実はissueやリリースノートに書かれているというケースがありました。(意外と探すのに時間がかかってしまった。。。)
  • Sendable 制約の影響範囲、特に継承を使っている箇所の把握... Actor対応が必要な箇所で継承が使えず設計変更が必要という話もあるので、、継承を使っているオブジェクトがあり、DIなどでSendable対応が必要な可能性がある場合はリファクタリング工数を想定したほうが良いです。
  • レビュー/テスト工数を見積もりに含める ... 上記の工数には含まれていないですが、レビュワー・情報共有を含め 1.5 人日以上かかったのと、QAチームからの工期が思いのほか伸びてしまいました。
  • SwiftConcurrencyのタスクなどの長期間かかるタスクは作業時間をチケットに逐次起票し、逐次状況を可視化したほうがよかったと感じました。

まとめ

Swift 6 の Strict Concurrency 対応は、単なる警告修正ではなく、設計・ツール・CI・レビュー体制まで含めた横断的な対応が必要になる作業でした。 コード量そのもの以上に、ライブラリ調査やフォーマッタ変更、QA 対応といった周辺作業が工数を押し上げる点は、事前に強く意識しておくべきだと感じています。 これから移行を検討される方は、早めに警告を炙り出しつつ、「どこで逃げて、どこは直すか」をチームで合意した上で進めるのがおすすめです。

参考

感覚的に理解するConcurrency: Swift 6はIsolationとSendableを用いてどのようにデータ競合を防止するか 事例別!Strict Concurrency対応方法

テコテックの採用活動について

テコテックでは新卒採用、中途採用共に積極的に募集をしています。 採用サイトにて会社の雰囲気や福利厚生、募集内容をご確認いただけます。 ご興味を持っていただけましたら是非ご覧ください。 tecotec.co.jp