◉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-07-03上級

Rork Max アプリでダウンロードをアプリ終了後も続ける — バックグラウンド URLSession の設計と再起動復帰

Rork Max のネイティブ Swift アプリで、アプリがサスペンド・終了されてもダウンロードを OS 側で継続させるバックグラウンド URLSession の設計を解説します。再起動復帰・resumeData・isDiscretionary の実測挙動まで動くコードで示します。

Rork Max236URLSessionバックグラウンド転送Swift48iOS115ダウンロード

✦ プレミアム記事

壁紙アプリで高解像度の画像パックを配信し始めた頃、レビュー欄に「ダウンロードが毎回途中で止まる」という報告が続いたことがあります。調べてみると、ユーザーはダウンロード開始後すぐホーム画面へ戻っており、およそ30秒後にアプリがサスペンドされた時点で転送が打ち切られていました。個人開発でサーバー側のログだけを眺めていても、この切断は「クライアント都合の切断」としか見えません。私自身、原因の切り分けに数日を費やしました。

答えはアプリ側の設計にありました。標準の URLSession はアプリのプロセスと運命を共にします。アプリが止まれば転送も止まる。これを避けるには、転送そのものを OS のデーモンに委ねる「バックグラウンドセッション」へ切り替える必要があります。

Rork Max はネイティブ Swift を生成するため、この仕組みをそのまま使えます。ただし通常のセッションと同じ感覚で書くと実行時エラーやファイル消失につながる制約がいくつもあります。以下、私が壁紙アプリの追加コンテンツ配信で実際に組んだ構成をもとに、設計の勘所を順に追っていきます。

転送を持つのはアプリではなく nsurlsessiond

バックグラウンドセッションの本質は「転送の実行主体がアプリのプロセス外に移る」ことです。実体は nsurlsessiond というシステムデーモンで、アプリがサスペンドされても、メモリ不足で終了されても、転送は OS 側で進み続けます。

通常セッションとの違いを先に整理しておきます。

観点通常セッションバックグラウンドセッション
転送の実行主体アプリのプロセス内nsurlsessiond(プロセス外)
アプリのサスペンド後転送は打ち切られる転送は継続する
アプリの終了後転送は消える転送は継続し、完了時にアプリが再起動される
completion handler 型の API使える使えない(実行時エラー)
delegate任意必須
dataTask使える原則不可(download / upload タスクのみ)

表の下2行が最初のつまずきどころです。session.downloadTask(with: url) { location, response, error in ... } のようなクロージャ渡しの API は、バックグラウンド構成のセッションでは呼んだ瞬間に例外で落ちます。アプリが生きていない間に完了する可能性がある以上、クロージャを保持し続けられないためで、すべての結果は delegate 経由で受け取る設計が強制されます。

セッションの生成と3つの制約

まずマネージャをシングルトンで用意します。identifier が同じセッションを複数生成すると挙動が不定になるため、生成箇所を一つに絞るのが安全です。

// BackgroundDownloadManager.swift
import Foundation
 
final class BackgroundDownloadManager: NSObject {
    static let shared = BackgroundDownloadManager()
    static let sessionIdentifier = "com.example.app.asset-downloads"
 
    /// アプリ再起動時に AppDelegate から渡される完了ハンドラ
    var backgroundCompletionHandler: (() -> Void)?
 
    private lazy var session: URLSession = {
        let config = URLSessionConfiguration.background(
            withIdentifier: Self.sessionIdentifier)
        config.sessionSendsLaunchEvents = true  // 終了後もアプリを起こす
        config.isDiscretionary = false          // 即時実行(後述)
        config.allowsCellularAccess = true
        return URLSession(configuration: config,
                          delegate: self,
                          delegateQueue: nil)
    }()
 
    func download(_ url: URL) {
        let task = session.downloadTask(with: url)
        // 期待サイズを伝えると OS のスケジューリングが安定します
        task.countOfBytesClientExpectsToReceive = 20_000_000
        task.resume()
    }
}

このコードが解決するのは「開始したダウンロードをアプリのライフサイクルから切り離す」ことです。押さえておく制約は3つあります。

第一に、delegate は初期化時にしか渡せません。後から差し替える API はないため、シングルトンの self を渡す構成が素直です。

第二に、sessionSendsLaunchEvents を true にしておかないと、アプリ終了後に転送が完了してもアプリは起こされません。デフォルトは true ですが、意図を明示するために書いています。

第三に、identifier はアプリ内で一意にします。機能ごとにセッションを分けたい場合は identifier も分け、それぞれの delegate を独立させます。

✦

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

この記事の続きを読む

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

この記事で得られること
✦アプリがサスペンド・終了されても OS 側で転送が続くダウンロード基盤を、動く Swift コードごと持ち帰れます
✦再起動をまたいで完了通知を受け取る handleEventsForBackgroundURLSession の配線と、SwiftUI アプリでの実装方法がわかります
✦resumeData による再開・一時ファイルの即時移動・isDiscretionary の実測挙動など、本番でつまずく箇所を先回りで塞げます
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

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

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

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

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

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

関連記事

⬡ 開発ツール2026-04-05
Rork Max × WidgetKit・ライブアクティビティ実装ガイド 2026 — Dynamic Island 対応まで
Rork Max で iOS Widget・ロック画面ウィジェット・Dynamic Island 対応のライブアクティビティを実装した記録。Timeline の焼き込み設計、APNs のヘッダーと優先度、push-to-start、stale-date の扱いまで実運用の判断を残します。
◇ アプリ開発2026-10-15
adjustsFontSizeToFit で特定の1行だけ極小になるとき、疑うのは文字列ではなく枠の高さです
一覧の10行目だけ文字が4ptまで縮む。データも文字列も正常なのに起きる iOS の症状を、Yoga の丸めと縮小判定のソースで確かめ、Node で再現率を数えた記録です。
◇ アプリ開発2026-04-26
Rork でネイティブ iOS アプリを作る — SwiftUI 生成から TestFlight 配信までの実践ワークフロー
Rork の新規 iPhone プロジェクトは Swift/SwiftUI で生成され、Rork Pro で App Store まで届きます。古い Expo プロジェクトとの見分け方、クレジットで見る費用、骨格プロンプト、実機確認、Xcode に降りる基準、TestFlight 提出までを手順で追います。