RORK LABEN
MAX — Rork MaxはReact NativeではなくネイティブSwiftアプリを生成し、iPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessageに対応しますNATIVE — Rork MaxではAR/LiDARスキャン・Metalによる3Dゲーム・ウィジェット・Dynamic Islandなどネイティブ機能を利用できますAPPLE — HealthKit・HomeKit・NFC・App Clips・オンデバイスCore MLといったAppleの高度な機能にもアクセスできますRN — 標準のRorkはReact Native(Expo)でiOSとAndroidを単一プロンプトから生成しますPRICE — 無料で始められ、有料プランは月額$25から利用できますGROWTH — Rorkは月間訪問743,000超・成長率85%に達し、$15Mの資金を調達しましたMAX — Rork MaxはReact NativeではなくネイティブSwiftアプリを生成し、iPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessageに対応しますNATIVE — Rork MaxではAR/LiDARスキャン・Metalによる3Dゲーム・ウィジェット・Dynamic Islandなどネイティブ機能を利用できますAPPLE — HealthKit・HomeKit・NFC・App Clips・オンデバイスCore MLといったAppleの高度な機能にもアクセスできますRN — 標準のRorkはReact Native(Expo)でiOSとAndroidを単一プロンプトから生成しますPRICE — 無料で始められ、有料プランは月額$25から利用できますGROWTH — Rorkは月間訪問743,000超・成長率85%に達し、$15Mの資金を調達しました
記事一覧/開発ツール
開発ツール/2026-07-09上級

Rork Max が書いた Swift を、Swift 6 の厳格な並行性検査に通す

Rork Max が生成した SwiftUI アプリを Swift 6 の complete concurrency checking に通した記録です。警告217件をターゲット単位で削り、@MainActor の置き場所と Sendable の壊し方を実測とともに整理しました。

Rork Max218Swift 6並行性SwiftUI62リファクタリング4

プレミアム記事

Xcode のビルド設定で Strict Concurrency Checking を complete に切り替えた瞬間、警告が 217 件出ました。生成直後は 1 件も出ていなかったコードです。

Rork Max が書いた SwiftUI アプリは、素直で読みやすいコードでした。ただそれは Swift 5 言語モードでの話でした。Swift 6 の言語モードに上げると、その素直さがそのまま「どのスレッドから触られるか誰も宣言していない」という指摘に変わります。

個人開発でアプリを 6 本並行運用している立場からすると、217 件はそれ自体が怖い数字ではありません。怖いのは、直し方を誤ると実行時の挙動が静かに変わることです。@MainActor をひとつ足すだけで、これまで背景スレッドで走っていた画像デコードが UI スレッドに引き戻される。警告は消えて、スクロールが 12fps になる。

この記事は、その 217 件を 3 週間かけて 0 にした過程の記録です。手順よりも「どこで判断を間違えかけたか」に紙幅を割きました。

まず一括で通そうとして失敗した

最初の 2 日間、私はプロジェクト全体の Swift 言語モードを 6 に上げて、出てきたエラーを上から順に潰そうとしました。

これは筋の悪いやり方でした。理由は 2 つあります。

ひとつは、エラーが連鎖するためです。ある型を Sendable に適合させると、その型を持つ別の型で新しいエラーが出ます。全体を一度に赤くすると、いま自分が直しているのが根本原因なのか、それとも波及した結果なのかが判別できなくなります。

もうひとつは、ビルドが通らない状態が長引くと、途中で挙動を確かめられないためです。並行性の修正は「コンパイルが通った=正しい」ではありません。@MainActor の付与は正真正銘の実行時セマンティクスの変更です。ビルドが 2 日間赤いまま進めた変更は、後から一件ずつ検証し直すはめになりました。

3 日目にやり直しました。プロジェクト全体ではなく、ターゲット単位で complete にする方針です。

// Package.swift — SPM ターゲットごとに段階を刻む
.target(
    name: "CoreModels",
    swiftSettings: [
        .swiftLanguageMode(.v6)          // 依存の葉から先に v6 へ
    ]
),
.target(
    name: "ImagePipeline",
    dependencies: ["CoreModels"],
    swiftSettings: [
        .swiftLanguageMode(.v5),         // まだ v5
        .enableUpcomingFeature("StrictConcurrency")  // 警告としてだけ観測
    ]
),

依存グラフの葉(他に依存しない層)から順に v6 へ上げます。上げ切ったターゲットは以降エラーが再発しません。まだ上げていないターゲットでは StrictConcurrency を upcoming feature として有効化し、警告としてだけ数を観測します。

このやり方に変えてから、赤い状態が続くのは 1 ターゲットぶんだけになりました。

v6 済みターゲット残警告備考
0(開始時)0 / 5217全体一括を断念
12 / 5134CoreModels / Persistence
24 / 541ImagePipeline / Networking
35 / 50App ターゲット

@MainActor は View ではなく状態層に置く

Rork Max が生成したコードは、SwiftUI の View@State と非同期処理が同居する構造でした。よくある形です。

// 生成直後: View が状態と通信の両方を抱えている
struct WallpaperGridView: View {
    @State private var items: [Wallpaper] = []
    @State private var isLoading = false
 
    var body: some View {
        ScrollView { /* ... */ }
            .task { await load() }
    }
 
    private func load() async {
        isLoading = true
        items = try! await WallpaperAPI.fetchAll()   // 隔離が宣言されていない
        isLoading = false
    }
}

complete にすると、WallpaperAPI.fetchAll() が返す [Wallpaper]View の隔離コンテキストへ渡す箇所で警告が出ます。

ここで最も手軽な対処は、WallpaperAPI@MainActor を付けることです。警告は消えます。そして fetchAll() の内部で走っていた JSON デコードが、メインスレッドに乗ります。

私はこれを一度やってしまい、初回ロード時のフレームドロップで気づきました。500 件のサムネイル情報をデコードする 180ms ぶんが、そのままメインスレッドを塞いでいました。

正しい置き場所は、View でも API でもなく、その間の状態層です。

// 移行後: 隔離の境界を Store に一本化する
@MainActor
@Observable
final class WallpaperStore {
    private(set) var items: [Wallpaper] = []
    private(set) var isLoading = false
 
    private let api: WallpaperFetching   // nonisolated なプロトコル
 
    init(api: WallpaperFetching) { self.api = api }
 
    func load() async {
        isLoading = true
        defer { isLoading = false }
        do {
            // await の向こう側はメインスレッドから離れる
            items = try await api.fetchAll()
        } catch {
            items = []
        }
    }
}
 
// API 側は隔離を持たない。呼ばれたスレッドで走る
protocol WallpaperFetching: Sendable {
    func fetchAll() async throws -> [Wallpaper]
}
 
struct WallpaperAPI: WallpaperFetching {
    func fetchAll() async throws -> [Wallpaper] {
        let (data, _) = try await URLSession.shared.data(from: Self.endpoint)
        return try JSONDecoder().decode([Wallpaper].self, from: data)  // 背景で走る
    }
}

WallpaperStore@MainActor を付け、WallpaperFetching は隔離を持たないままにします。try await api.fetchAll() の呼び出しでメインアクターは一度手放されるため、デコードは背景スレッドで実行されます。戻り値を items に代入する時点でメインアクターに戻ります。

この形にすると、WallpaperSendable である必要が生じます。それは正しい要求です。値がアクター境界を越えるからです。

判断基準として私が置いたのは次の 1 行でした。隔離注釈は「その値を最終的に誰が読むか」ではなく「その値の可変状態を誰が所有するか」に付ける。 View は読むだけです。所有しているのは Store です。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
警告217件を3週間で0にした、ターゲット単位の段階移行手順とビルド設定
@MainActor を View ではなく状態層に置いた判断基準と、その前後の差分
nonisolated(unsafe) を許容する3条件と、残り12箇所を追跡し続ける仕組み
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

開発ツール2026-07-06
Rork Max の SwiftUI を @Observable へ移す — ObservableObject で広がっていた再描画を絞り込む
Rork Max が生成する SwiftUI アプリは ObservableObject と @Published をよく使いますが、状態が一つ変わるだけで購読しているビュー全体が再評価されます。Observation フレームワークの @Observable へ移すことで、プロパティ単位に再描画を絞り込む手順と、Instruments で実測したビュー実行回数の変化を、個人開発の作業ログとしてまとめました。
開発ツール2026-05-02
Rork Max が生成した SwiftUI コードを本番品質まで磨き上げる10のリファクタリング・パターン
Rork Max が出力した SwiftUI コードはそのままでは App Store 審査で通らないことがあります。クラッシュやリジェクトの原因となる10のパターンを、ビフォー/アフターのコード例とともに解説します。
開発ツール2026-06-30
Rork Max で StandBy モードに最適化したウィジェットを実装する
充電中の横向き表示で使われる StandBy モード向けに、WidgetKit ウィジェットを最適化する実装手順です。常時表示の調光・ナイトモード・コンテナ背景の扱いを、Rork Max が生成したコードへどう手で加えるかまで含めて解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →