Xcode - TECH PLAY - TECH PLAY

TECH PLAY

Xcode

イベント

該当するコンテンツが見つかりませんでした

マガジン

該当するコンテンツが見つかりませんでした

技術ブログ

はじめに こんにちは、プラットフォームエンジニアリング部ネイティブブロックの とし です。普段はZOZOTOWN iOSアプリの基盤開発を担当しています。今回は現在取り組んでいるZOZOTOWN iOSアプリのSwift 6移行の進め方と方針について紹介します。 Swift 6移行について最初に自分が着手した時「難しそう」「よくわからない」「本当にここまで大きく変えるのか」と思い、正直かなり腰が重かったです。ただ、実際にはSwift 6で何を達成したいのかという背景を深掘りしながら進めていくことで、ぼんやりと輪郭が見えてきた気がします。Swift 6移行は、完璧を求めて対応しようとするとかなり大変です。しかし、頑張りすぎず、まずはできることから始めれば、一歩ずつ進めていけると感じました。 なお、2026年10月時点でZOZOTOWNのSwift 6移行はまだ完了していません。 目次 はじめに 目次 背景 なぜSwift 6移行が難しいのか 頑張りすぎないための選択1:最新の仕組みに乗っかる Xcodeを最新にする ビルド設定を2つ変える 頑張りすぎないための選択2:基本はMainActorで動かす 頑張りすぎないための選択3:モジュールごとに小さく試す 頑張りすぎないための選択4:全部は対応しない まとめ 背景 まず、Swift 6移行を始める前のZOZOTOWN iOSアプリの状況を紹介します。 2026年4月時点で、ZOZOTOWN iOSアプリはSwift 6.2コンパイラ(Xcode 26.2)を使いつつ、Swift 5言語モードで動いていました。Swift Concurrencyへの対応はまだ行われていない状態でした。 言語モードを上げる前に、移行の規模感を数字で把握することにしました。Swift 5言語モードのままStrict Concurrency CheckingをCompleteにすると、警告が2,500件以上出ていました。 正直、この2,500件の警告をすべて対応してからSwift 6言語モードへと上げるのは、あまり現実的ではないと感じていました。また、Swift 6への理解が深いメンバーもあまりおらず、チームとしてキャッチアップする必要がありました。 なぜSwift 6移行が難しいのか ではいったいなぜ、Swift 6移行が難しいのでしょうか?その理由は下記の難しさが同時にやってくるからです。 言語仕様の理解が難しい async/await、Actor isolation、Structured concurrencyといった難しい概念をきちんと理解する必要がある 何が正解なのかがわからない アーキテクチャはAppleが定義していないので、どこまでをUIレイヤーとしてMainActorで実行し、何にnonisolatedを付け、何をactorで定義するかを自分たちで決める必要がある Swift 6.0と6.2で設計思想が変わっており、過去のWWDCの説明が今の前提と合わないこともある チームの理解度が追いついていない 一部の人が理解するだけでは足りず、メンバー全員が同じ前提で書ける状態にそろえる必要がある 実は、最初は正攻法で進めようとしていました。Strict Concurrency CheckingをTargetedにして、出てきた警告を頭から順に直していく進め方です。しかし、自分を含めてチームの対応はまったく進みませんでした。チームとしてどう進めるかの方針が決まっておらず、警告をどう直すべきかもよく分からなかったためです。 振り返ると、この進め方は3つの難しさを最初からすべて背負うものでした。 そこで私たちは、難しさを一度に引き受けるのをやめ、分けて受け取ることにしました。この方針で取った頑張りすぎないための選択は、下記の4つです。 選択1:最新の仕組みに乗っかる(対応済み) 選択2:基本はMainActorで動かす(対応済み) 選択3:モジュールごとに小さく試す(進行中) 選択4:全部は対応しない(進行中) それぞれの詳細は、次章から順に紹介します。 頑張りすぎないための選択1:最新の仕組みに乗っかる 選択の1つ目は、自分たちで頑張って対応する前に、Swiftの最新の仕組みに乗っかることです。 Swift 6.0の時点では、データ競合を防ぐための対応が開発者にとって負担の大きいものになっていました。Swiftチーム自身もこの点を認めていて、Swift 6.2ではデータ競合の安全性を取り入れやすくする仕組みが入っています。開発者が楽をするために用意された仕組みなので、まずはそこへ乗っかることにしました。 github.com Xcodeを最新にする 設定を変える前に、まずXcodeを当時の最新(Xcode 26.5)にしました。 XcodeとSwiftコンパイラは強く結びついています。同じXcodeのメジャーバージョンでも、中のSwiftコンパイラのマイナーバージョンは異なることがあります。マイナーバージョンが違えば、診断の出方が違ったり、不具合が直っていたりします。Concurrency関連は、まだコンパイラ側のバグや挙動の違いが多く見られる領域です。そのため、コードに手を入れる前に、できるだけ最新バージョンのコンパイラを使うのがおすすめです。 ZOZOTOWNでも実際に、Approachable Concurrencyを有効にするとエラーが出るかどうかがXcodeのバージョンで変わりました。Swift 6移行の一歩目は、Xcodeのアップデートです。 ビルド設定を2つ変える Xcodeを更新したら、ビルド設定を2つ変えました。Approachable Concurrencyを有効にし、メインのアプリターゲットだけDefault Actor IsolationをMainActorにしました。APIClientやログ基盤をはじめとした他のモジュールは、デフォルト値のnonisolatedのままです。言語モードはどちらもSwift 5のままです。 Approachable Concurrencyとは、並行処理に関する複数のupcoming featureをまとめて有効にするXcodeのビルド設定です。含まれる機能の1つがNonisolatedNonsendingByDefault( SE-0461 )で、nonisolatedなasync関数の実行先を呼び出し元にそろえます。 これまでは、nonisolatedなasync関数を呼ぶと、処理が呼び出し元のactorから離れていました。そのため、MainActorから引数や戻り値を受け渡すたびに、その型をSendableにする必要がありました。SE-0461を有効にすると、処理は呼び出し元のactorのまま実行され、actorの境界をまたぎません。その結果、Sendableを付けなくても済む場面が増えます。 Default Actor Isolationとは、隔離先を明示しない宣言をどこに隔離するかを決めるXcodeのビルド設定です。MainActorにすると、何も書いていない宣言はデフォルトでMainActorに隔離されます( SE-0466 )。これらはSwift 6.2(Xcode 26)から入った機能になります。 Xcodeのプロジェクトでは、ターゲットのBuild Settingsにある「Swift Compiler - Concurrency」から、この2つの設定を変更できます。 Swift Packageで管理しているモジュールでは、Package.swiftのswiftSettingsで指定します。Package.swiftにはApproachable Concurrencyをまとめて有効にする設定がありません。そのため、含まれるupcoming featureを1つずつ有効にしています。ZOZOTOWNではモジュールのデフォルトの隔離先をnonisolatedのままにしているので、defaultIsolationにはnilを指定しています。 // swift-tools-version: 6.2 import PackageDescription let package = Package( name : "SampleModule" , targets : [ .target ( name: "SampleModule" , swiftSettings: [ // Default Actor Isolationに関する設定 // MainActor.self(MainActor)かnil(nonisolated)が入る .defaultIsolation( nil ), // XcodeのApproachable Concurrencyに含まれる機能を1つずつ有効にしている .enableUpcomingFeature( "NonisolatedNonsendingByDefault" ), .enableUpcomingFeature( "InferIsolatedConformances" ), .enableUpcomingFeature( "InferSendableFromCaptures" ), .enableUpcomingFeature( "DisableOutwardActorInference" ), .enableUpcomingFeature( "GlobalActorIsolatedTypesUsability" ) ] ) ], // Swift 5言語モードでビルドする swiftLanguageModes : [ .v5 ] ) この2つの設定は、移行の最初に入れました。どちらもコードの書き方の前提を決める設定です。途中で前提が変わると「このコードは今どのactorで動いているんだっけ」とメンバーが迷い、キャッチアップのコストが高くなります。 最初から入れられたのは、ZOZOTOWNのConcurrency対応がほとんど進んでいなかったからです。既存コードに書き方の前提がまだなかったので、設定で前提を決めてしまえました。対応が進んでいなかったプロダクトだからこそ取れた選択肢です。 また、Approachable Concurrencyに含まれる機能は、今後デフォルトで有効になっていくと予想しています。Xcode 26で新しく作成したプロジェクトでも、この2つの設定はすでに有効です。先に合わせておけば、将来もう一度前提を変えずに済みます。 頑張りすぎないための選択2:基本はMainActorで動かす 選択の2つ目は、処理をどこで実行するかを厳密に設計しすぎず、基本はMainActorで動かすことです。 選択1でDefault Actor IsolationをMainActorにしたアプリターゲットでは、何も書いていないコードはMainActorで動きます。データ競合の心配は減る一方で、本来メインスレッドで動かす必要のない処理までMainActorに集まります。そこで、どこまでをMainActorで実行するかを決める必要がありました。 検討の中では、UI関連以外のすべてにnonisolatedを付けて厳密に書き分ける案もありました。Swift 6の設計思想には一番忠実ですが、付ける対象が膨大になり、Sendable対応も連鎖します。何より、メンバー全員がisolationの使い分けを理解していることが前提になります。 最終的には、パフォーマンスが気になる部分以外はMainActorのままとする方針へ寄せました。この方針を決めるにあたり、実機でInstruments(HangsとTime Profiler)を使ってパフォーマンスを計測しました。その結果、ほとんどの処理をMainActorで実行しても問題ないことを確認しています。重いJSONのデコードなど、計測でメインスレッドの負荷が問題になった箇所だけ、 @concurrent を付けてMainActorから処理を逃がします。それ以外ではnonisolatedを付けず、アプリターゲットのコードはすべてMainActorで実行しています。性能面では厳密に付ける案の方が有利ですが、メンバーが今までの書き方から特別に意識を変えることなく、Swift 6へ移行できることを目標にしました。 最初から完璧な書き分けを求めず、チームメンバーに無理をさせないことも、本記事で言う「頑張りすぎない」の1つです。 頑張りすぎないための選択3:モジュールごとに小さく試す ZOZOTOWNには、メインのターゲットの他に、APIClientやログ基盤をはじめとしたいくつかのモジュールがあります。 選択の3つ目は、Swift 6言語モードへの切り替えをモジュール単位で順番に進めることです。この作業は、現在も進行中です。 アプリ全体を一度にSwift 6言語モードへ上げる道は取りませんでした。モジュール単位で進めれば、1回の切り替えで向き合うエラーはそのモジュールの分だけに収まります。 モジュールの大きさもさまざまです。小さいモジュールもあれば、大きいモジュールもあります。そこで、とにかくシンプルで小さいモジュールからSwift 6言語モードへ切り替えていきました。 小さいモジュールから始めたことで、次のメリットがありました。 対応する箇所が少ないので、対応しやすい 小さいモジュールで得た知見をためて、次のモジュールの対応に生かせる 一度に大きな変更をリリースせず、モジュールごとにこまめにリリースできるので、プロダクトへの影響を抑えられる モジュールを小さく区切ったうえで、もう1つ頑張りすぎないために決めたのが、選択4です。 頑張りすぎないための選択4:全部は対応しない 選択の4つ目は、すべての警告を解消してからSwift 6言語モードに上げるやり方を取らないことです。特に一番大きい本体ターゲットでは、この方針を積極的に活用していく想定です。 Swift 6言語モードの目的は、データ競合をコンパイルの段階でコンパイラに検知してもらうことです。もちろん、すべて対応できるのであれば、対応した方が良いです。ただ、既存コードをすべて対応させてから上げるのは、特に大きいモジュールではあまりに大変です。 そこで、既存コードと新しく書くコードを分けて扱います。 既存コードで対応しきれない箇所には、 @preconcurrency や @unchecked Sendable を付けたうえでSwift 6言語モードに上げる 新規追加・変更するコードは、Swift 6のコンパイラにデータ競合を検知してもらう @preconcurrency や @unchecked Sendable を付けた箇所は、コンパイラの検査から外れます。安全性を手放しているように見えますが、Swift 5言語モードのままの状態と比べても、コンパイル時の検査に差分はありません。 Swift 5言語モードでも、データ競合の恐れがある箇所は存在しています。コンパイラがそれを特定していないだけです。つまり、Swift 5言語モードのままにしておくことは、Swift 6言語モードで @preconcurrency などを付けることと同じ状態です。 むしろ、Swift 6言語モードに上げてこれらを付けておけば、データ競合の恐れがある箇所に目印が付きます。あとから対応するときも、目印を手がかりに探せます。 全部対応し終えるまでSwift 5言語モードで待つより、先に上げてしまった方が、コンパイラに守ってもらえる範囲は早く広がります。 ただし、 @unchecked Sendable はスレッド安全性を私たち開発者が保証する宣言です。使う場所は既存コードに絞り、スレッド安全性を私たちで保証できる型にだけ付けます。 実際にやってみると、不都合が出てくるかもしれません。その時は、その時に対応すれば良いと考えています。問題が起きてから考えると割り切ることも、頑張りすぎないための1つです。 まとめ 本記事では、ZOZOTOWN iOSアプリのSwift 6移行で私たちが取った、頑張りすぎないための4つの選択を紹介しました。 Swift 6移行が難しいことは、取り組んでみて改めて感じました。それでも、完璧を求めず頑張りすぎないことを意識したことで、最初の一歩を踏み出し、歩き始めることができました。 ここまで書いた内容はあくまで現在の方針で、今後変えることも十分にあると思います。だからこそ、Swift 6移行に腰が重くなっている方がいれば、ぜひ頑張りすぎず、できそうなところから始めてみてください。 ZOZOでは、一緒に大規模なサービス作りをしてくれる方を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください! corp.zozo.com
本記事は、2026 年 10 月 14 日に Mike Landi と Doug Clauson が公開した「 Local sandboxing in Kiro: keep agent commands within boundaries 」を翻訳したものです。 Kiro のローカルサンドボックスは、ローカルのファイルや認証情報を保護する機能です。エージェントが実行するコマンドやスクリプトについて、アクセスできる範囲を制限します。この制限はオペレーティングシステムが強制するため、誤ったコマンドや信頼できないスクリプトによる影響を小さく抑えられます。本機能は、現在 Kiro IDE と Kiro CLI で一般提供中です。 多くのお客様は、Kiro IDE や CLI のエージェントをローカルマシン上で直接実行しています。ローカルで実行すると、エージェントはあなたと同じファイルシステム、インストール済みのツールチェーン、localhost のサービスを参照できます。そのため、エージェントは離れた場所からプロジェクトについて推測する代わりに、実際にビルドしてテストすることが可能です。その代償は、エージェントが実行するすべてのコマンドがあなたの権限を持ち、あなたとして動作することです。コマンドはファイルシステムにもネットワークにもフルアクセスできます。また、オペレーティングシステムのレベルには、エージェントが選んだコマンドとあなた自身が入力したコマンドを区別する仕組みがありません。これまでは、あなたが自分の注意力でこうしたコマンドを制御してきました。セッションを見守り、承認する前に各コマンドを読んで、おかしなものがあれば止めるという方法です。 エージェントを見張る注意力がボトルネックになる理由 エージェントの能力が上がり、開発者が並列で動かすエージェントが増えるにつれて、あなたの注意力がシステム全体のボトルネックになりかねません。タスクが長くなればコマンドが増え、コマンドが増えれば承認疲れが起きます。2 つ目のセッションを始めたり、リファクタリングの途中で席を外したりすると、次のどちらかになります。コマンドがブロックされたまま止まるか、あなたがコマンドを読まなくなるかです。さらに、問題を引き起こすのは悪意のある攻撃者だけではありません。たった 1 回の誤ったツール呼び出しや意図の取り違えが、実際のインシデントにつながる可能性があります。しかも、これまでエージェントが実行できたアクションの範囲は広いものでした。たとえば、Kiro の信頼モデルでは、エージェントが read_file ツールで ~/.aws や ~/.ssh を読み取ることをすでに拒否しています。しかし、粘り強いエージェントがシェル経由でスクリプトを実行すれば、同じファイルを読めてしまう可能性がありました。要求はブロックされても、エージェントは別の方法でファイルを読み取れたのです。IDE、CLI、Web で利用できる Kiro のクラウドサンドボックスは、エージェントを隔離された環境で実行することでこの問題に対処しました。ただし、ローカルにとどめる必要がある作業も数多くあります。たとえば、データレジデンシーの規則によりコードをクラウドに置けない場合や、localhost のサービスや VPN の内側にあるリソースを使う場合です。Xcode と Apple のツールチェーンが必要な iOS ビルドも該当します。 ローカルサンドボックスの仕組み ローカルサンドボックスでも、エージェント自体はこれまでどおりネイティブに動作します。一方、エージェントがあなたの代わりに実行するコマンドは、カーネルが強制するサンドボックスを経由する仕組みです。使用するサンドボックスは、macOS では Seatbelt、Linux では bubblewrap、Windows では AppContainer です。Kiro のツール呼び出しに対する権限チェックに加えて、オペレーティングシステムがこれらの制限を強制します。サンドボックスに与える権限は、あなたが選べます。たとえば、書き込みできるのはプロジェクトディレクトリと一時領域だけです。カーネルは、よく使われる認証情報のパスの読み取りを拒否します。そのため、エージェントが ~/.ssh/id_rsa を要求しても、判断を挟むまでもなく拒否されます。この拒否は、要求が read_file 、シェルのワンライナー、読み取りを代行させるために書かれたスクリプトのどれを経由した場合でも同じです。ネットワークアクセスは、ドメイン単位でフィルタリングするローカルプロキシを必ず経由します。この仕組みは、クラウドサンドボックスですでに使われているモデルと同じです。エージェントはパッケージレジストリなどの承認済みサービスにアクセスでき、それ以外の宛先へのアクセスはブロックされます。 エージェントをサンドボックスの外に置く理由 私たちは、エージェント自体を意図的にサンドボックスの外に置きました。制限されるのはエージェントが実行するコマンドだけです。そのため、セッションの途中でサンドボックスのオンとオフを切り替えたり、セッションを再起動せずにポリシーを変更したりできます。境界を変更しても、次のコマンドをどのようにラップするかが変わるだけだからです。この設計判断から 3 つの利点が得られます。まず、マシンの ID はサンドボックスの中に入りません。マシンの ID とは、エージェントがモデルを呼び出したり Kiro のバックエンドにアクセスしたりするときに使う認証情報です。そのため、サンドボックス内のプロセスが暴走しても、その認証情報を使ってあなたの名義で外部と通信することはできません。次に、ローカルの MCP サーバーはホストのネイティブプロセスとして、変更なしでそのまま動作します。MCP サーバーの動作も制限したい場合は、サンドボックスを経由させることもできます。最後に、従来の意味でのサンドボックスは存在せず、あるのは制限された子プロセスだけです。つまり、サンドボックスに何かを注入する必要もなければ、最初のコマンドを実行する前にコンテナ、イメージ、VM を起動する必要もありません。 サンドボックスによる保護を有効にすると、ローカルでの作業はオペレーティングシステムが強制する制限の範囲内で行われます。リファクタリング、ビルドとテストのループ、依存関係のアップグレードは、これまでどおり手元のマシンで実行されます。ただし、これらの作業で実行されるコマンドがアクセスできるファイルとネットワークの宛先は、制限の対象です。サンドボックスを有効にすると、承認済みのコマンドが実行できる操作を制限できます。 有効化する 現時点では、サンドボックスはデフォルトで無効になっています。ワークスペースで有効にするには、 sandbox.json を使用してください。グローバルのデフォルトとして設定する場合は ~/.kiro/settings/sandbox.json に、プロジェクト単位で上書きする場合は <project-root>/.kiro/settings/sandbox.json に配置してください。 { "enabled": true } ファイルを編集せずに有効にすることもできます。Kiro CLI では、ローカルセッション( kiro-cli chat --v3 )で /sandbox on を実行するか、 /sandbox を開いてグローバルまたはプロジェクトのスコープとネットワークモードを選択してください。Kiro IDE では、コマンドパレットから Kiro: Open User Sandbox Config (JSON) を実行してください。有効になると、ステータスバーに Command sandbox: Active と表示されます。Windows では、最初に 1 回だけ管理者権限でのセットアップが必要です。IDE ではセットアップを促すプロンプトが表示されます。CLI では /sandbox setup を実行してください。 デフォルトのポリシーを適用するには、ここまでの設定で十分です。デフォルトのポリシーでは、プロジェクトディレクトリへの書き込みが許可され、一般的な認証情報のパスへのアクセスは拒否されます。ネットワークアクセスはローカルプロキシを経由します。エージェントが触れられる範囲を細かく調整したい場合は、 permissions.yaml にルールを追加してください。このファイルは、現在すでにツールの認可を管理しているポリシーファイルと同じものです。ローカルサンドボックスは、既存の Workspace Trust モデルとは別に 2 つ目の権限システムを導入するものではありません。同じ fs_read 、 fs_write 、ネットワークに関する capability ルールが、ツールの境界に加えて OS カーネルでも強制されるようになります。以下は ~/.kiro/settings/permissions.yaml の例です。 rules: - capability: fs_write match: ["./**"] effect: allow - capability: fs_read match: ["~/.aws/**", "~/.ssh/**"] effect: deny - capability: sandbox_network match: ["internal-registry.corp.example.com"] effect: allow Workspace Trust と permissions.yaml でポリシーを設計する エージェントによる ~/.ssh/id_rsa への read_file ツール呼び出しを拒否するルールは、カーネルレベルでのサンドボックスの読み取りも拒否します。エージェントがそのファイルにたどり着く手段が read_file 、シェルのワンライナー、読み取りを代行させるために自分で書いたスクリプトのどれであっても同じです。1 つの信頼ポリシーが 2 回強制されます。1 回目はエージェントがツール呼び出しを通じて行儀よく要求したとき、2 回目は OS がその下で実際のシステムコールをチェックするときです。ツール呼び出しのチェックは必要ですが、それだけでは十分ではありません。ポリシーエンジンが認識しているツールを経由したリクエストしか捕捉できないからです。カーネルのチェックは、前述の Python スクリプトによる回避策を含め、それ以外のリクエストをすべて捕捉します。サンドボックスが読み込むのは、既存のポリシーそのものです。そのため、ポリシーエンジンがすでにサポートしているすべてのスコープ(管理者、ユーザー、ワークスペース、エージェント、セッション)が自動的に引き継がれます。 よくある質問 デフォルトで有効になっていますか? いいえ。グローバルに有効にするには ~/.kiro/settings/sandbox.json 、特定のプロジェクトで有効にするには <project>/.kiro/settings/sandbox.json を使用してください。CLI で /sandbox on を実行するか、IDE のサンドボックス設定から有効にすることもできます。 どのオペレーティングシステムがサポートされていますか? macOS では Seatbelt を使用します。Linux では bubblewrap を使用します。bubblewrap は socat と一緒に、お使いのディストリビューションのパッケージからインストールしてください。Windows では、1 回限りの管理者権限でのセットアップを行ったうえで AppContainer を使用します。 Kiro が承認を求めなくなるのですか? いいえ。サンドボックスは、承認済みのコマンドで実行できる操作が制限されています。 ローカルサンドボックスは Kiro IDE と CLI で利用できます。設定の詳細やネットワークモードについては local command sandbox docs を、 sandbox_network ルールについては permissions docs を参照してください。 試すには、Kiro IDE 1.3 以降、または Kiro CLI 2.31 以降(ローカルの V3 セッション)にアップデートしてください。 翻訳は Solutions Architect の吉村が担当いたしました。
こんにちは。株式会社タップルでiOSアプリエンジニアをしている五味と、同じく株式会社タップルでiOS ...

動画

該当するコンテンツが見つかりませんでした

書籍