ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
常に最新のコンプリケーションを
時間厳守はとても重要です:昼夜問わず、Apple Watchのコンプリケーションから関わりのある情報を送り続け、ユーザーに必要な情報を必要なタイミングで届ける方法をお伝えします。アプリケーションのランタイムを活用するための成功事例、バックグラウンドアプリケーションリフレッシュ、URLSessionといったAPIを統合する方法、適切なタイミングでプッシュ通知を実行する方法についてもお知らせします。
リソース
関連ビデオ
WWDC21
WWDC20
-
ダウンロード
こんにちは ようこそ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() }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。