View in English

  • メニューを開く メニューを閉じる
  • Apple Developer
検索
検索を終了
  • Apple Developer
  • ニュース
  • 見つける
  • デザイン
  • 開発
  • 配信
  • サポート
  • アカウント
次の内容に検索結果を絞り込む

クイックリンク

5 クイックリンク

ビデオ

メニューを開く メニューを閉じる
  • コレクション
  • トピック
  • すべてのビデオ
  • 利用方法

その他のビデオ

ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。

  • 概要
  • トランスクリプト
  • コード
  • 常に最新のコンプリケーションを

    時間厳守はとても重要です:昼夜問わず、Apple Watchのコンプリケーションから関わりのある情報を送り続け、ユーザーに必要な情報を必要なタイミングで届ける方法をお伝えします。アプリケーションのランタイムを活用するための成功事例、バックグラウンドアプリケーションリフレッシュ、URLSessionといったAPIを統合する方法、適切なタイミングでプッシュ通知を実行する方法についてもお知らせします。

    リソース

    • ClockKit
    • Creating and updating a complication’s timeline
    • URLSession Programming Guide
    • WatchKit
      • HDビデオ
      • SDビデオ

    関連ビデオ

    WWDC21

    • 再往復: Apple Watch上のデータ転送

    WWDC20

    • Apple Watchのコンプリケーションの作成
    • SwiftUIでコンプリケーションを構築する
    • Watch Face Sharingとの出会い
  • このビデオを検索

    こんにちは ようこそWWDCへ “常に最新のコンプリケーションを” Apple Watchチームのソフトウェアエンジニア マイク・ラムです 本日はコンプリケーションを 常に最新の状態に保つ方法をご紹介します コンプリケーションはApple Watchの ユーザーエクスペリエンスに組み込まれています 文字盤の共有やSwiftUIのコンプリケーション 複数のコンプリケーションAPIと共に watchOS 7でリリースされます コンプリケーションは今年 注目の Watch向けアプリケーションです Watchの文字盤に様々な情報を表示する機能を コンプリケーションと呼んでいます 快適に使用するためにコンプリケーションは 最新の状態に保ちましょう watchOSはコンプリケーション アプリケーションの特別な機能を提供します 他のアプリケーションが停止し メモリから削除された時でも動き続けます システムがそれを 止める必要があったとしても その後で再起動され 更新されます

    コンプリケーションは常に表示されているので コンプリケーションアプリケーションは 個人情報の取り扱いが考慮されています アプリケーションが動的コンプリケーションを 最新の状態に保つための方法を見ていきましょう サンプルのアプリケーションを 説明することから始めましょう

    アプリケーションがアクティブに 使用されている時が コンプリケーションの更新に いいタイミングです では やってみましょう アプリケーションがフォアグラウンドで 動いていない時でも watchOSはコンプリケーションを 常に最新の状態に保つことができます バックグラウンド更新は まるで魔法のように素晴らしいです 1つ目のバックグラウンド更新機能は “Background App Refresh”です Background App Refreshにより アプリケーションは バックグラウンドランタイムをスケジュールして WatchのAPIとデータに アクセスできるようになります さらに アプリケーションが動いていない間に サーバからデータを取得するために URLSessionをバックグラウンドで スケジュールすることも出来ます その時 データは コンプリケーションのPushを使用して Watchのアプリケーションに直接送られます この方法はセッションの最後に説明します

    重要なポイントは 3つの各バックグラウンド機能のために アプリケーションが アクティブである必要がない点です 更新する必要がある時に コンプリケーションは起動します これら全ての機能は アプリケーションの必要に応じて 別々にも 組み合わせても使用できます コンプリケーションを最新の状態に保つ方法を “kite flying app”を使って説明します 我々は電話を必要としないでいられる体験を 提供することを目指し 独立したWatch向けの アプリケーションを構築しています 複数のアクティブなコンプリケーションを サポートするために watchOS 7で利用可能な 新しいAPIを使用します 繰り返しになりますが 本日紹介するのは コンプリケーションを最新の状態に保つ方法です 設計や構築の方法については 他のセッションをご覧ください 我々は人々がアプリケーションによって より活動的になることを願っているので 最初のコンプリケーションはHealthKitから 1日の活動についての情報を取り出します データを取り出すために Background App Refreshを使います 凧揚げをするには天気を知りたいですね 特に風についての気象データが必要になります バックグラウンドでURLSessionを使用し 最後にキャッシュされた場所の気象データを 定期的に得ることができます 凧揚げを盛り上げるために 友達からの声援を 表示できるコンプリケーションはいかがでしょう コンプリケーションの更新には コンプリケーションのPushを使います まずはフォアグラウンドでの 更新について話しましょう ユーザーがアプリケーションを起動している時は コンプリケーションを更新するのに適しています 表示したい更新がある時はコンプリケーションの タイムラインをリロードするために ClockKitのAPIを使用します コンプリケーションは タイムラインのエントリーで構成されています アプリケーションが コンプリケーションの更新を求めると そのコンプリケーションのreloadTimelineを コンプリケーションのサーバに呼び出します

    ここに書いたのは updateActiveComplicationsメソッドです アプリケーション全体において使うものです

    このメソッドはアクティブな コンプリケーションの配列を繰り返し 1個ずつリロードすることを コンプリケーションのサーバに要求します このサンプルでは 毎回全ての コンプリケーションを更新しますが 通常は 更新が必要なものだけを 更新すればいいです コンプリケーションを更新する時は updateActiveComplicationsを呼び出します

    reloadTimelineが updateActiveComplicationsに呼び出された後 コンプリケーションサーバはアプリケーションの CLKComplicationDataSourceを呼び出します 現在のタイムラインのエントリーを得るためです

    その後 テンプレートとプロバイダを使用して アプリケーションはエントリーを作成します

    エントリーを作成し終えると 提供された完了ハンドラを使い それを コンプリケーションのサーバに渡します

    これがコードです reloadTimelineが updateActiveComplicationsに呼び出された後 CLKComplicationDataSourceが 呼び出されます 更新するコンプリケーションの 現在の タイムラインのエントリーが要求されます

    ハンドラは それが構築された後 エントリーを返すために提供されます

    コンプリケーションのタイプに適した テンプレートを使用します テンプレートにプロバイダを入力して タイムラインのエントリーを作成します エントリーが作成されたら 提供された ハンドラを使ってエントリーを返します フォアグラウンドでの コンプリケーションの更新は簡単です コンプリケーションのサーバに コンプリケーションのリロードを要求し それから1個ずつ 現在のエントリーを供給します これはユーザーが プリケーションで選択を変更するか アプリケーションがフォアグラウンドで 新しいデータを受信するたびに行います

    しかし アプリケーションが フォアグラウンドで動いていない時もあります その場合はコンプリケーションを 更新するために必要なデータを得るために Background App Refreshのような 機能を使います

    サンプルのアプリケーションに戻って アクティビティのコンプリケーションのために HealthKitからデータを取得してみましょう

    Background App Refreshにより 定期的な更新をスケジュールすることで アプリケーションが使用されていない場合でも コンプリケーションは最新の状態に保たれます

    アプリケーションは コンプリケーションを更新するために 1時間に4回まで Background App Refreshを使えます この回数は アプリケーションの コンプリケーションが アクティブな文字盤に 設定される回数に関わらず変わりません アプリケーションが受け取る 更新の実際の回数は 他のプロセスが実行された回数や バッテリーの使用量などの状況に左右されます

    Background App Refreshを スケジュールするために scheduleBackgroundRefreshが WKExtentionに呼び出されます これはバックグラウンドで アプリケーションが起動していても可能なので applicationDidFinishLaunchingで 最初のリクエストをスケジュールしましょう

    Background App Refreshを スケジュールするためのメソッドを書きました まず予定日を選びます これが最初のリクエストです すぐにリクエストします システムはアプリケーションを起動する 正しい時間を選びます これはリクエストした時間以降になります 通常1分か2分以内ですが システムの状況によります あなた自身のデータを提供するためには userInfo dictionaryを使います この場合 実行される方法を示すために リクエストが行われた時間を渡します

    予定日とオプションのuserInfoを得ると WKExtentionに scheduleBackgroundRefreshを呼び出します

    リクエストがスケジュールされると WKExtentionは非同期で完了ハンドラを メインスレッドに呼び出します 発生した可能性のあるエラーを処理します

    タスクが準備されると アプリケーションがアクティブになり バックグラウンドのタスクをハンドルするために ExtentionDelegateが呼び出されます

    処理の後 コンプリケーションの 更新をリクエストし 次のBackground App Refreshを スケジュールして タスクの完了を設定します

    では ExtentionDelegateを見てみましょう XcodeがExtentionDelegateを生成すると backgroundTasksをハンドルするための メソッドも生成されます

    システムには 完了する必要のある 複数のタスクがある場合があります ExtentionDelegateは全てのタスクをループし 1つずつ呼び出します

    生成されたコードはデフォルトのハンドラを アプリケーションが受け取ることができる 全タイプのバックグラウンドタスクに提供します

    WKApplicationRefreshBackgroundTaskを ハンドリングして デフォルトのハンドラを 我々のコードに置き換えています

    ここでは例としてリクエストのスケジュール時に 追加したuserInfoを検索します リクエストからの時間を計算するために その中に記憶した日付を使用します その後updateActiveComplicationsメソッドを 呼び出して アクティブなコンプリケーションのリロードを コンプリケーションのサーバに要求します それから次のバックグラウンド更新を スケジュールします コンプリケーションを更新し 別のリクエストをスケジュールすれば 現在のタスクは完了します スナップショットは必要でないことを示す “false”になります それぞれのコンプリケーションの更新は スナップショットのリクエストにつながるため 個々にリクエストする必要はありません

    アプリケーションはバックグラウンドのタスクが 完了次第 すぐに停止するかもしれないので タスクの完了を設定する前に 全ての処理を行う必要があります

    HealthKitへのアクセスなど より複雑なことをするには ストラテジーを変更する必要があります

    ExtentionDelegateに コードを入れすぎないために HealthKitを使用する データプロバイダを追加しました そして それ自身の完了ハンドラを 受け取るメソッドを追加しています

    HealthKitのクエリは 非同期になる可能性があります そのため 更新が完了するまで待ってから コンプリケーションを更新し 次のリクエストをスケジュールして タスクの完了を設定する必要があります

    これを簡単に実行することを可能にするのが HealthKitの作業を行う 新しいHealthDataProviderです

    このデータを更新するために HealthDataProviderを呼び出します 非同期で行います

    データの更新が終わると コンプリケーションが更新したかを問う 完了ハンドラを呼び出します 完了ハンドラの中で 実際にコンプリケーションを更新し 次の更新をスケジュールし そしてタスクの完了を設定します

    つまりBackground App Refreshは 定期的なバックグラウンドのタスクを スケジュールするのに最適なのです これらのタスクをハンドルするために 1時間に最大4回までアプリケーションを 再開もしくは起動することができます 覚えておくべきガイドラインを 挙げましょう

    一度にリクエストできる 未処理のリクエストの数は1つだけです 定期的な更新を必要であれば ご紹介した手順を実行してください そして現在の更新を完了する前に 次の更新をスケジュールします ネットワークのアクティビティは 許可されていません WatchでほとんどのAPIが使用可能ですが URLSessionは例外です もしURLSessionを使おうとしても エラーになります

    アプリケーションはアクティブなCPU時間の 最大4秒までに制限されています 短いと思われるかもしれませんが 一定の処理の4秒間は実はかなり長いのです それより長い処理が必要な場合は 小さいチャンクに分割してください

    アプリケーションがタスクを完了するための 合計時間は最大15秒です 15秒を超える理由として一般的なのは タスクの完了を設定し忘れていることです

    バックグラウンドでネットワークから データにアクセスする必要がある場合は Background URLSessionを使ってください Background URLSessionは アプリケーションを実行していない時でも アプリケーションをスケジュールして データを受信できるようにします

    Background App Refreshに加えて Background URLSessionが使えます リクエストを素早く変更することや 認証チャレンジを挿入することも出来ます

    追加予定の ローカライズされた “風”のコンプリケーションで 気象情報を検索するために それを使います ほとんどの状況下で アプリケーションは 1時間に4回までリクエストを送受信できますが 実際の回数は Wi-Fiの利用や 携帯電話の電波― バッテリーの寿命などの要因に 左右されます

    複数の未処理のバックグラウンドでの ダウンロードタスクが実行できます アプリケーションの起動時は 常にセッションにアタッチして URLSessionデリゲートコールバックを 受信できるようにしてください まずBackground URLSessionを スケジュールする方法を説明しましょう

    このアプリケーションでは 定期的に気象データを得たいの URLSessionのフレームワークを使います URLSessionデリゲートになる WeatherDataProviderを作成しました データプロバイダはBackground URLSessionの コンフィギュレーションを作成します バックグラウンドで アプリケーションを動かすために バックグラウンドのコンフィギュレーションに sessionSendsLaunchEventを設定します ダウンロードタスクでURLSessionを作成するため コンフィギュレーションを使用します タスクの一番早い開始日を設定します タスクがスケジュールされている日付になります 次にタスクを再開始して開始します これがWeatherDataProviderの最初の部分です URLSessionDownloadDelegateです URLSessionを作成するためには 次のようにします バックグラウンドの コンフィギュレーションを取得 “nondiscretionary”として コンフィギュレーションを設定 sessionSendsLaunchEventsが “true”に設定してあることを確認 これでアプリケーションは バックグラウンドで起動します それからURLSessionを作成するために コンフィギュレーションを使います そしてdelegateQueueを “nil”に設定する場合は URLSessionのレスポンスはバックグラウンドの シリアルキューに送られます WeatherDataProviderを引き続き使用して scheduleメソッドを追加しました これによりダウンロードタスクを作成し スケジュールすることができます 未処理のリクエストは複数がありますが この場合1つしか必要ないため 未処理のリクエストがない限り 新しいバックグラウンドタスクを スケジュールします Core Locationから最後にキャッシュされた 場所を使って必要なURLを構築し バックグラウンドのセッションのための ダウンロードタスクを作成します このタスクに一番早い開始日を設定します 最初のリクエストをすぐに作成し 後続のリクエストを 15分ごとにスケジュールします 送受信する予定のバイト数を設定し 最後に実行準備をするタスクを 再開始します スケジュールした後 ダウンロードタスクは アプリケーションから独立して実行します 完了したら アプリケーションは バックグラウンドで再開始するか リクエストをハンドルするために 必要に応じて起動します

    ダウンロードが完了すると WKExtentsion WKURLSessionRefreshBackgroundTaskを extension delegateに送ります

    リクエストをハンドルするために WeatherDataProviderを使用します

    タスクが終了したことを設定するまで URLSessionのデリゲートメソッドは処理されます これらの呼び出しをハンドリングする前に タスクの完了を設定しないでください

    タスクが正常に完了したなら デリゲートは didFinishDownloadingToデリゲートの 呼び出しを受け取ります ダウンロードが 正常に完了したかどうかにかかわらず デリゲートは didCompleteWithOptionalErrorを受け取ります 呼び出しが完了すると WeatherDataProviderが 与えられた完了ハンドラを呼び出します そしてコンプリケーションを更新し 新しいリクエストをスケジュールして それからタスクの完了を設定します

    スケジュールしたダウンロードタスクが 完了したら 次は結果をハンドルします セッションのデリゲートは WKURLSessionRefreshBackgroundTaskを ハンドルすることを要求されます セッションのデリゲートは WeatherDataProviderに更新を要求し 完了時に呼び出されるclosureを渡します

    更新が完了すると WeatherDataProviderがclosureを呼び出します そのclosureの中で次の検索をスケジュールし 必要に応じてコンプリケーションを更新し それからタスクの完了を設定します WeatherDataProviderの中で refreshメソッドが― 完了ハンドラを保存します スケジュールされたデリゲートメソッドが 実行されたら呼び出せます WeatherDataProviderは タスクが完了されてから URLSession delegate method downloadTask didFinishDownloadingToを受け取ります 要求したデータはファイルに ダウンロードされるのでそれを確認します それから受け取った json気象データを処理します

    ダウンロードタスクが完了し データが処理されると アプリケーションは didCompleteWithOptionalErrorを受け取ります 完了ハンドラをメインキューに呼び出したいので メインキューにディスパッチし 完了ハンドラを呼び出します エラーが全く無ければ コンプリケーションを更新しています その時 完了ハンドラを“nil”に設定すれば それ以上呼び出されません

    リクエストによってはダウンロードの完了前に アプリケーションが中間リクエストを受け取り アプリケーションがURLを更新したり ダウンロードタスクをキャンセルさせたり 認証チャレンジに応答できたりします

    これらを簡単に見ていきましょう これまで見てきたURLSessionタスクに関連する 他のケースのように WKURLSessionRefreshBackgroundTaskとして これらはアプリケーションに処理されます ExtentionDelegateはこれらのタスクを ハンドルすることを要求されます これらのリクエストをハンドルするために WeatherDataProviderを使います タスクがアクティブな間に WeatherDataProviderは URLSessionサブシステムから デリゲートの呼び出しを受け取ります

    WillBeginDelayRequestにより アプリケーションで― URLのリクエストの更新や キャンセルができます 例えば もしリクエストを開始してから 長時間が経過したなら 最初にリクエストを行ったときに 指定した場所を Core Locationの最後に キャッシュされた場所に置き換えます

    DidReceiveChallengeが アプリケーションに発生する― 可能性のある 認証チャレンジに応答します デリゲートがSessionDidFinishEventsを 受け取るのは 全てのイベントが処理された時であり これが完了ハンドラを呼び出す時です

    この時 新しいタスクを スケジュールしたいかもしれませんが 現在のタスクが完了していないので やめておきましょう その代わりに このタスクが完了したとして設定します このガイドラインは Background App Refreshのものと同じです

    負荷のかかる処理は避けるべきです そして 処理を受けてから15秒以内に タスクの完了を設定してください Background URLSessionは遠隔のサーバから データを検索することに優れています 必要に応じてスケジュールしたり 修正またはキャンセルしたり出来ます

    本日紹介する最後の機能は コンプリケーションのPushです ユースケースによっては Pushはサーバから取り出すデータよりも より効率的です

    これは特にイベントドリブン型データに顕著です コンプリケーションのPushを使い 最新のコンプリケーションにデータを供給します これにより 凧揚げをする仲間からの 声援を追跡することができます サーバは各Watchに1日に最大50個まで コンプリケーションのPushを送れます コンプリケーションのPushは 等間隔にする必要はありません データがバースト伝送になる場合は 今までに説明した他の機能よりも リクエストが速く送られるでしょう 1日の容量を超過することを防ぐために 調整する必要があるかもしれません

    WatchにPushを送っているサーバは 適切な認証を受ける必要があります では簡単に その設定方法を 見てみましょう まずアプリケーションの バンドルIDを含む識別子が必要です “.watchkitapp.complication”を 後ろに続けます これは重要です アプリケーションIDが 正しい形式になっていない場合 PushはAppleのサーバにより拒否されるか Watchで受信されないかもしれません

    .complicationの付いた アプリケーションIDが作成されたら Apple Push Notification service SSL証明書を 作成するためにそれを使います サーバはその証明書を使用して AppleのPush通知サーバを認証します

    アプリケーションには WatchKit extensionの リモート通知バックグラウンドモードと Push通知機能も必要です

    Watch向けでは PushKitに登録するために PushNotificationProviderを使います

    登録が成功しすると アプリケーションは認証を受け取ります これらの資格情報をサーバにアップロードして サーバがWatchと通信できるようにします これはPushNotificationProviderで PKPushRegistryDelegateです PKPushRegistryのインスタンスを作成し コールバックにメインキューを供給します それ自身をデリゲートとして設定します

    またdesiredPushTypeを.complicationに 設定します これはサーバに作成してインストールした .complication識別子と証明書に一致します

    登録が完了するとレジストリは資格情報と didUpdate pushCredentials呼び出しを返します これらの資格情報をサーバに送信し アプリケーションのインスタンスと 通信できるようにします

    Watchがその認証を サーバにアップデートしたら サーバはこれらの資格情報を使用して そのWatchのPushをAppleサーバに送信します AppleサーバはそれらをWatchに送ります 各Watchに1日あたり50個のPushが可能です

    コンプリケーションのPushを送る時は バックグラウンドPushと同じように フォーマットして aps dictionaryにある content-availableのエントリーを提供します PushがWatchでPushKitにより受け取られると didReceiveIncomingPushWithPayloadで アプリケーションに渡されます Pushの処理が終了した後の呼び出しのために 完了ハンドラが用意されています

    PushNotificationProviderの実装に戻ります Pushが可能になると アクティブでない場合 アプリケーションは再開始または起動されます

    didReceiveIncomingPush payload for typeが デリゲートに呼び出されます PushKitへの登録時に指定するキューは この呼び出しを行うために使用されます

    この場合はこれがメインキューです

    完了ハンドラが提供されます ペイロードが処理された時に呼び出されます ペイロードを処理し extension delegateを呼び出して コンプリケーションを更新します これで全てのコンプリケーションを 更新しています もしこれがshippingアプリケーション だったなら 必要なコンプリケーションだけを 更新したいでしょう ではコンプリケーションの Pushについて復習していきます アプリケーションの各インスタンスは 1日50個まで受け取れます 複数のアクティブな コンプリケーションが― アプリケーションにあっても その数は変わりません

    ガイドラインは他のタイプの バックグラウンド更新の場合と同じです 本日説明した技術をまとめます アクティブな時はフォアグラウンドの状態で コンプリケーションを更新してください 特にこれを行うべき時は 入力に応じて アプリケーションの 状態が変化する場合や フォアグラウンドの状態の時に サーバからデータを取り出す場合です

    アプリケーションがフォアグラウンドから バックグラウンドに移動する時に 処理を完了するため ProcessInfoの アクティビティを使用します

    Background App Refreshは HealthKitなどのwatchOS APIの使用や あなた自身のデータへのアクセスのための ランタイムをスケジュールするのに最適です バックグラウンドの更新は1時間に4回まで コンプリケーションの更新のために使用できます

    サーバからデータを取り出すために Background URLSessionのタスクは 1時間に4回まで スケジュールすることが出来ます アプリケーションが動いている時は 常にデリゲートを再アタッチしてください そうすることで保留になっているかもしれない 更新を受け取ることが出来ます

    Push通知は1日50回まで サーバから各Watchへ送られます データがバースト伝送になる場合は 一定の間隔を置くかもっと頻繁に送ってください 51回目以降の通知は無視されるため 必要に応じてサーバにスロットルを適用します これらの機能を必要に応じて 個別にまたは組み合わせて使用してください コンプリケーションを常に最新状態に保つことが いかに重要であるか分かるでしょう アプリケーションがアクティブでない時でも コンプリケーションを常に 最新の状態に保つための機能が複数あります 最高の体験のために 本日学んだ技術を組み合わせてみてください 本日説明した全ての機能は 独立した Watch向けアプリケーションで使用できます “Meet Watch Face Sharing”セッションは 最新のコンプリケーションAPIを 詳しくを説明しています “Build Complications in SwiftUI”は SwiftUIを使用してコンプリケーションを 構築する方法を説明しています 昨年の“Creating Independent Watch Apps”も 含め 是非ご覧になってください これらの技術を使用して コンプリケーションを構築してみてください 引き続き WWDCをお楽しみください

    • 3:32 - updateActiveComplications

      class ExtensionDelegate: NSObject, WKExtensionDelegate {
      
          func updateActiveComplications() {
      
             let complicationServer = CLKComplicationServer.sharedInstance()
      
              if let activeComplications = complicationServer.activeComplications {
      
                  for complication in activeComplications {
      
                     complicationServer.reloadTimeline(for: complication)
      
                  }
              } 
          }
      }
    • 4:26 - getCurrentTimelineEntry

      class ComplicationController: NSObject, CLKComplicationDataSource {
      
          func getCurrentTimelineEntry(for complication: CLKComplication, 
              withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
      
              switch (complication.family) {
        
              case .modularSmall:
                 let template = CLKComplicationTemplateModularLargeTallBody.init(
                                     headerTextProvider: headerTextProvider, 
                                     bodyTextProvider: bodyTextProvider)
      
                  entry = CLKComplicationTimelineEntry(date: Date(), 
                              complicationTemplate: template)
              }
      
              handler(entry)
          }
      }
    • 6:06 - scheduleBar

      private func scheduleBAR(_ first: Bool) {
              let now = Date()
              let scheduledDate = now.addingTimeInterval(first ? 60 : 15*60)
      
              let info:NSDictionary = [“submissionDate”:now]
      
              let wkExt = WKExtension.shared()
              wkExt.scheduleBackgroundRefresh(withPreferredDate: scheduledDate, userInfo:info)
              { (error: Error?) in
                  if (error != nil) {
                      print("background refresh could not be scheduled \(error.debugDescription)")
                  } 
              }
         }
    • 7:08 - handleBAR

      class ExtensionDelegate: NSObject, WKExtensionDelegate {
          func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
      
              for task in backgroundTasks {
      
                switch task {
                case let backgroundTask as WKApplicationRefreshBackgroundTask:
      
                      if let userInfo:NSDictionary = backgroundTask.userInfo as? NSDictionary {
                         if let then:Date = userInfo["submissionDate"] as! Date {
                            let interval = Date.init().timeIntervalSince(then)
                            print("interval since request was made \(interval)")
                         }
                      }
      
                      self.updateActiveComplications()
      
                      self.scheduleBAR(first: false)
      
                      backgroundTask.setTaskCompletedWithSnapshot(false)
    • 8:47 - handleBAR (DataProvider)

      class ExtensionDelegate: NSObject, WKExtensionDelegate {
      
          var healthDataProvider: HealthDataProvider
      
          func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
              for task in backgroundTasks {
                  switch task {
                  case let backgroundTask as WKApplicationRefreshBackgroundTask:
      
                      healthDataProvider.refresh() { (update: Bool) -> Void in
                          if update {
                              self.updateActiveComplications()
                          }
                          self.scheduleBAR(first: false)
                          backgroundTask.setTaskCompletedWithSnapshot(false)
                      }
    • 11:35 - Instantiate backgroundURLSession

      class WeatherDataProvider : NSObject, URLSessionDownloadDelegate {
      
          private lazy var backgroundURLSession: URLSession = {
              let config = URLSessionConfiguration.background(withIdentifier: “BackgroundWeather")
              config.isDiscretionary = false
              config.sessionSendsLaunchEvents = true
      
              return URLSession(configuration: config, delegate: self, delegateQueue: nil)
          }()
    • 12:02 - Schedule backgroundURLSessionTask

      func schedule(_ first: Bool) {
      
              if backgroundTask == nil {
      
                  if let url = self.currentWeatherURLForLocation(delegate.currentLocationCoordinate)
                  {
                      let bgTask = backgroundURLSession.downloadTask(with: url)
      
                      bgTask.earliestBeginDate = Date().addingTimeInterval(first ? 60 : 15*60)
      
                      bgTask.countOfBytesClientExpectsToSend = 200
                      bgTask.countOfBytesClientExpectsToReceive = 1024
      
                      bgTask.resume()
      
                      backgroundTask = bgTask
                  }
              }
          }
      }
    • 13:29 - handle backgroundURLSession

      class ExtensionDelegate: NSObject, WKExtensionDelegate {
      
         var weatherDataProvider:WeatherDataProvider
      
          func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
             for task in backgroundTasks {
                 switch task {
                  
                      case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
      
                          weatherDataProvider.refresh() { (update: Bool) -> Void in
                              weatherDataProvider.schedule(first: false)
                              if update {
                                  self.updateActiveComplications()
                              }
                              urlSessionTask.setTaskCompletedWithSnapshot(false)
                          }
    • 13:59 - handle backgroundURLSession

      class WeatherDataProvider : NSObject, URLSessionDownloadDelegate {
      
          var completionHandler : ((_ update: Bool) -> Void)?
      
          func refresh(_ completionHandler: @escaping (_ update: Bool) -> Void) {
      
              self.completionHandler = completionHandler
      
          }
    • 14:08 - didFinishDownloadingTo

      class WeatherDataProvider : NSObject, URLSessionDownloadDelegate {
      
          func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
                          didFinishDownloadingTo location: URL) {
      
              if location.isFileURL {
                  do {
      
                      let jsonData = try Data(contentsOf: location)
                      if let kiteFlyingWeather = KiteFlyingWeather(jsonData) {
                          // Process weather data here.
                      }
      
                  } catch let error as NSError {
                      print("could not read data from \(location)")
                  }
              }
          }
    • 14:23 - didComplete

      func urlSession(_ session: URLSession, task: URLSessionTask, 
                           didCompleteWithError error: Error?) {
      
              print("session didCompleteWithError \(error.debugDescription)”)
      
              DispatchQueue.main.async {
      
                 self.completionHandler?(error == nil)
      
                  self.completionHandler = nil
      
              }
          }
      }
    • 17:53 - Complication Pushes

      class PushNotificationProvider : NSObject, PKPushRegistryDelegate {
      
          func startPushKit() -> Void {
              let pushRegistry = PKPushRegistry(queue: .main)
              pushRegistry.delegate = self
              pushRegistry.desiredPushTypes = [.complication]
          }
        
          func pushRegistry(_ registry: PKPushRegistry, 
                            didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
              // Send credentials to server 
          }
        
          func pushRegistry(_ registry: PKPushRegistry, 
                              didReceiveIncomingPushWith payload: PKPushPayload, 
                              for type: PKPushType, completion: @escaping () -> Void) {
              // Process payload
              delegate.updateActiveComplications()
              completion()
          }

Developer Footer

  • ビデオ
  • WWDC20
  • 常に最新のコンプリケーションを
  • メニューを開く メニューを閉じる
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    メニューを開く メニューを閉じる
    • アクセシビリティ
    • アクセサリ
    • App Extension
    • App Store
    • オーディオとビデオ(英語)
    • 拡張現実
    • デザイン
    • 配信
    • 教育
    • フォント(英語)
    • ゲーム
    • ヘルスケアとフィットネス
    • アプリ内課金
    • ローカリゼーション
    • マップと位置情報
    • 機械学習
    • オープンソース(英語)
    • セキュリティ
    • SafariとWeb(英語)
    メニューを開く メニューを閉じる
    • 英語ドキュメント(完全版)
    • 日本語ドキュメント(一部トピック)
    • チュートリアル
    • ダウンロード(英語)
    • フォーラム(英語)
    • ビデオ
    Open Menu Close Menu
    • サポートドキュメント
    • お問い合わせ
    • バグ報告
    • システム状況(英語)
    メニューを開く メニューを閉じる
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles(英語)
    • フィードバックアシスタント
    メニューを開く メニューを閉じる
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(英語)
    • News Partner Program(英語)
    • Video Partner Program(英語)
    • セキュリティ報奨金プログラム(英語)
    • Security Research Device Program(英語)
    Open Menu Close Menu
    • Appleに相談
    • Apple Developer Center
    • App Store Awards(英語)
    • Apple Design Awards
    • Apple Developer Academy(英語)
    • WWDC
    Apple Developerアプリを入手する
    Copyright © 2025 Apple Inc. All rights reserved.
    利用規約 プライバシーポリシー 契約とガイドライン