◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-04-05上級

Rork Max × Swift Concurrency 実装ガイド——async/await・Actor・Structured Concurrency でアプリを根本から変える

Rork Max で Swift Concurrency を完全活用するための実践ガイド。async/await の正しい使い方から Actor によるデータ競合防止、Structured Concurrency・TaskGroup・AsyncStream まで、プロダクション品質のコードを体系的に解説します。

Swift Concurrencyasync/awaitActorRork Max236iOS開発15パフォーマンス33

✦ プレミアム記事

取り組みの背景:なぜ今 Swift Concurrency を学ぶのか

Rork Max で iOS アプリを本格開発していると、必ずといっていいほど直面するのが「非同期処理の複雑さ」です。API 呼び出し、データベースアクセス、画像の読み込み——これらをすべて安全・効率的に扱うには、Swift Concurrency の深い理解が不可欠です。

Swift 5.5(iOS 15以降)で導入された Swift Concurrency は、単なる糖衣構文ではありません。async/await、Actor、Structured Concurrency、TaskGroup、AsyncStream といった機能が有機的に連携し、データ競合を型システムレベルで防ぎながら、直感的に読めるコードを実現する仕組みです。

この記事で学べること

  • async/await の仕組みと、GCD(Grand Central Dispatch)との根本的な違い
  • Actor によるデータ競合の防止と @MainActor の正しい使い方
  • TaskGroup を使った並列処理と、適切なエラーハンドリング
  • AsyncStream でリアルタイムデータを扱うパターン
  • Rork Max のネイティブモジュールとの統合方法

対象読者

  • Rork Max でネイティブ(Swift)コードを書いたことがある方
  • GCD や OperationQueue には慣れているが、Swift Concurrency への移行を検討中の方
  • データ競合やクラッシュの根本原因を理解したい方

async/await の基礎と GCD との違い

GCD が抱える本質的な問題

Swift Concurrency を理解するには、まず GCD が「何を解決できなかったか」を知る点が肝心です。

// GCD の典型的な書き方(Rork Max のネイティブモジュール内)
@objc func fetchUserData(_ userId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
    DispatchQueue.global(qos: .userInitiated).async {
        // ① ここからバックグラウンドスレッド
        guard let url = URL(string: "https://api.example.com/users/\(userId)") else {
            // ② reject はメインスレッドで呼ぶ必要があるのか?バックグラウンドで呼んでいい?
            reject("INVALID_URL", "Invalid URL", nil)
            return
        }
 
        URLSession.shared.dataTask(with: url) { data, response, error in
            // ③ このクロージャはどのスレッドで動く?
            if let error = error {
                reject("FETCH_ERROR", error.localizedDescription, error)
                return
            }
 
            guard let data = data else {
                reject("NO_DATA", "No data received", nil)
                return
            }
 
            DispatchQueue.main.async {
                // ④ UI更新のためにメインスレッドに切り替え
                resolve(String(data: data, encoding: .utf8))
            }
        }.resume()
    }
}

このコードには潜在的な問題が複数あります。スレッドの切り替えを手動で管理しなければならず、コールバックのネストが深くなると「どのスレッドで動いているか」の追跡が困難になります。また、reject や resolve を二重に呼び出してしまうバグも起きやすい構造です。

async/await で書き直す

// Swift Concurrency での書き方
@objc func fetchUserData(_ userId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
    Task {
        do {
            let data = try await UserAPI.fetchUser(userId: userId)
            resolve(data)
        } catch {
            reject("FETCH_ERROR", error.localizedDescription, error)
        }
    }
}
 
// 実際のデータ取得ロジック(再利用可能な形で分離)
enum UserAPI {
    static func fetchUser(userId: String) async throws -> String {
        guard let url = URL(string: "https://api.example.com/users/\(userId)") else {
            throw APIError.invalidURL
        }
 
        // URLSession は async/await に対応済み
        let (data, response) = try await URLSession.shared.data(from: url)
 
        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw APIError.invalidResponse
        }
 
        return String(data: data, encoding: .utf8) ?? ""
    }
}
// 期待する動作: データが取得できれば resolve が呼ばれ、エラーがあれば reject が呼ばれる
// スレッドの切り替えは Swift ランタイムが自動的に最適化する

async/await の重要な特性として、await は「スレッドをブロックしない」という点があります。GCD の semaphore.wait() や DispatchGroup.wait() とは根本的に異なり、現在のスレッドを解放して他の処理を実行させつつ、再開条件が満たされたら効率よく再開します。


✦

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

この記事の続きを読む

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

この記事で得られること
✦GCD 直列1,180ms を async let 並列318msへ短縮した実測ベンチマークと、最遅 API に律速される並列化の限界
✦@MainActor を継承した Task が重い処理でメインスレッドを占有する落とし穴と、detached への正しい逃がし方
✦actor reentrancy によるキャッシュ二重取得を inFlight タスク共有で防ぐ実装、および GCD からの6ステップ移行チェックリスト
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

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

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

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

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

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

関連記事

⬡ 開発ツール2026-05-17
Rork Max の SwiftUI 機能を壁紙アプリ開発で検証した結果 — 実際に動いた機能と手を入れた機能
運用中の壁紙アプリの本番コードを基準に、Rork Max の SwiftUI ネイティブ生成を検証しました。そのまま使えた機能、手を入れた機能、生成を諦めた機能を、動くコードと実機での計測値とともにお伝えします。
⬡ 開発ツール2026-04-28
Rork Max でスクリーンタイム制御アプリを実装する手順 — Family Controls / DeviceActivity / ManagedSettings
Rork Max でスクリーンタイム系アプリを実装する実践ガイド。Family Controls の認可、DeviceActivity の時間帯監視、ManagedSettings によるアプリブロックを Rork のカスタムネイティブモジュールに組み込む全工程を、ハマりどころとともに解説します。
⬡ 開発ツール2026-08-02
2文字の検索語が静かに0件になる — Expo アプリの日本語検索インデックスを実測で選び直す
SQLite FTS5 の trigram トークナイザは2文字以下の日本語クエリを例外なしで0件にします。2万件のカタログで線形走査・bigram 転置索引・FTS5 を実測し、索引を足すべき閾値と起動コストの実数を出した記録です。