◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Expo SDK 58 が 10/15 に正式リリースされました(Beta は 09/15 から)。上げる前に依存の対応状況を確認しましょう●SDK 58 ISSUE — Android ウィジェットが定期更新されない、Stripe が静的リンクになるなど初期報告が出ています●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 — Expo SDK 58 が 10/15 に正式リリースされました(Beta は 09/15 から)。上げる前に依存の対応状況を確認しましょう●SDK 58 ISSUE — Android ウィジェットが定期更新されない、Stripe が静的リンクになるなど初期報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-09-05上級

Widget Extension でも BGTaskScheduler.submit は成功します。走らないのは register が親アプリだけのものだからです

Widget Extension から BGTaskScheduler に依頼を出すとビルドも submit も通るのに、翌朝ウィジェットは変わりません。register がホストアプリ専用である事実から、更新の主語を親アプリへ戻す設計まで、壁紙アプリの運用で詰めた手順を残します。

Rork594WidgetKit11BGTaskScheduler4iOS115Expo223App Extension3

✦ プレミアム記事

夕方になるとホーム画面のウィジェットが昨日の一枚のまま止まる、という問題を片づけた翌週のことでした。次に届いた要望は「アプリを一度も開かない日でも、朝には次の壁紙に変わっていてほしい」というものです。

素直に考えれば、更新の主語はウィジェット側にあります。表示しているのはウィジェットなのですから、次の画像を用意する仕事も同じターゲットに持たせればよい——そう考えて、Widget Extension のコードに BGAppRefreshTaskRequest を組み込みました。

ビルドは通りました。submit も true を返しました。それなのに、翌朝の画面は前日のままでした。原因にたどり着くまで半日かかっております。

submit は通り、register だけが拒まれます

先に結論をお伝えします。BGTaskScheduler は、拡張から依頼を出すことは許していますが、依頼を受け取る側になることは許していません。SDK のヘッダには registerForTaskWithIdentifier:usingQueue:launchHandler: に対して拡張では使えない旨の注釈が付いており、その理由が「Only the host application may register launch handlers」と一行だけ添えられています(BackgroundTasks.framework の BGTaskScheduler.h)。

同じヘッダの説明文には、拡張から依頼を出せる場合があること、そして背景作業のために起動されるのはホストアプリだけであることが、続けて書かれています。つまり submit の成功は「受け付けました」以上の意味を持ちません。

呼び出しWidget Extension から実際に起きること
submitTaskRequest:error:呼べます依頼は受理され、戻り値は true になります
registerForTaskWithIdentifier:...呼べません拡張では利用できない指定が付いています
起動されるターゲット—ホストアプリのみで、拡張は起動されません

私が書いてしまったのは、次のような拡張側のコードでした。動きはします。ただし、受け手がどこにもいません。

// WallpaperWidget/RefreshScheduler.swift(この設計は行き止まりです)
import BackgroundTasks
 
enum RefreshScheduler {
    static func scheduleFromWidget() {
        let request = BGAppRefreshTaskRequest(identifier: "net.dolice.wallpaper.dailyRefresh")
        request.earliestBeginDate = Calendar.current.date(byAdding: .hour, value: 6, to: Date())
        do {
            try BGTaskScheduler.shared.submit(request)
            // ここは通ります。true が返り、ログにも「予約できた」と出ます。
            // しかし launchHandler は拡張に登録できないため、誰も起きてきません。
            NSLog("[widget] submitted dailyRefresh")
        } catch {
            NSLog("[widget] submit failed: \(error)")
        }
    }
}

submit が例外を投げないので、ログには成功しか残りません。落ちないぶん、気づくのが遅れます。私はこの種の無言の失敗をいちばん警戒しております。

本番運用でいちばん怖いのは、対処のしようがある不具合ではなく、こうして誰にも報告されない取りこぼしなのかもしれません。クラッシュしていれば計測に乗りますが、これは乗りません。

Info.plist の台帳は親アプリ側にしかありません

もう一つ、拡張に更新を持たせようとすると必ずぶつかる壁があります。識別子の許可リストです。

BGTaskScheduler の NotPermitted(Code=3)は、UIBackgroundModes に適切なモードが含まれていないか、識別子が BGTaskSchedulerPermittedIdentifiers に無い場合に返ります。ここで大事なのは、その配列が置かれるべき場所がアプリの Info.plist だという点です(BGTaskScheduler.Error.Code.unavailable の説明 と併せて読むと、Code=1 と Code=3 の役割分担がはっきりします)。

拡張の Info.plist に書いても効きません

Xcode のターゲット一覧で Widget Extension を選び、そちらの Info.plist に BGTaskSchedulerPermittedIdentifiers を足したくなります。私も一度そうしました。エラーは何も出ません。何も起きないだけです。

Rork や Expo の構成では app.config 側に書きます

Rork が出力するのは React Native と Expo の構成ですので、Info.plist を手で編集するのではなく、設定ファイル側に寄せます。

// app.config.js — 親アプリの Info.plist に入る場所へ書きます
export default {
  expo: {
    ios: {
      bundleIdentifier: "net.dolice.wallpaper",
      infoPlist: {
        UIBackgroundModes: ["fetch", "processing"],
        BGTaskSchedulerPermittedIdentifiers: [
          "net.dolice.wallpaper.dailyRefresh"
        ]
      }
    }
  }
};

識別子は 1 か所で定義して両方から参照します

親アプリと拡張で識別子の文字列がずれると、register と submit が別々のタスクを指すことになり、これも無言で外れます。私は共有ターゲットに定数を置いて、両方からそれを参照する形にしております。

// Shared/RefreshTaskID.swift — 親アプリと Widget Extension の両方に含めます
public enum RefreshTaskID {
    public static let daily = "net.dolice.wallpaper.dailyRefresh"
    public static let appGroup = "group.net.dolice.wallpaper"
    public static let widgetKind = "DailyWallpaperWidget"
}
✦

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

この記事の続きを読む

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

この記事で得られること
✦Widget Extension に背景更新を持たせようとして半日溶かす前に、register と submit のどちらが自分の構成で禁じられているかを 5 分で判別できるようになる
✦BGAppRefreshTask の実行機会と WidgetKit の reload という2つの予算を切り分けて設計し、親アプリからウィジェットへ確実に橋渡しできるようになる
✦submit が true を返したのに更新が来ないとき、getPendingTaskRequests と App Group の受信記録のどちらを先に見るかを迷わず決められるようになる
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

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

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

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

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

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

関連記事

⬡ 開発ツール2026-05-28
Rork アプリで iOS の BGTaskScheduler.submit が Error Code=1 で失敗するときの切り分け手順
BGTaskScheduler.submit が Error Code=1 を返すとき、原因は 6 種類に収まります。Code=1 と Code=3 の読み分け、Expo 構成に固有の identifier、ウィジェット拡張から submit してしまう落とし穴、getPendingTaskRequests による自己確認まで整理しました。
⬡ 開発ツール2026-09-21
exit code 0 で止まるビルド — ログを黙らせていた設定と、空ファイルを通した存在チェック
eas build --local が exit code 0 but produced no further output で止まる件と、0 バイトのスタブが埋め込まれず dyld で落ちる件。どちらも原因は出力を減らす設定でした。自分のスクリプトを棚卸しする手順まで書き残します。
⬡ 開発ツール2026-07-27
最低サポート iOS をいつ上げるか — 利用率ではなく、コードに残った分岐の数で決める
最低サポート OS の引き上げを利用率で判断すると、毎年同じ結論になって先送りが続きます。OS 分岐に注記を付けて棚卸しし、iOS と Android の引き上げ先ごとに消せる分岐を数えるスクリプトと、引き上げ後に見る指標をまとめました。