WWDC Quick Look 💓 By SwiftGGTeam
Keep your complications up to date

Keep your complications up to date

元の動画を見る

ハイライト

watchOS は、ClockKit タイムラインを更新する 4 つの機会を提供します。フォアグラウンド リフレッシュ、バックグラウンド アプリ リフレッシュ、バックグラウンド URLSession、コンプリケーション プッシュです。通常、バックグラウンド更新は 1 時間あたり最大 4 回、コンプリケーションのプッシュは 1 ウォッチあたり 1 日あたり最大 50 回で、アプリはデータ ソースに応じて更新リズムを制御する必要があります。

主要内容

Apple Watchのコンプリケーションは常に文字盤にあります。ユーザーが手首を上げたときに表示されるのはアプリのステータスであり、必ずしも最初にアプリを開くとは限りません。したがって、watchOS は、複雑なアプリにいくつかの特別な機会を与えます。アプリはシステムによって保持され、必要に応じてウォッチフェイスのコンテンツを更新するために再起動されます (00:43)。

この話の範囲は非常に狭く、すでに複雑な問題が発生している後、ユーザーがアプリを開いていないときに新しいデータを表示し続けるようにする方法です。このサンプル アプリは、ウォッチフェイスに 3 種類の情報を備えた独立した Watch アプリです。HealthKit からのアクティビティ データの取得、サーバーからの天気と風速の取得、友人からの励ましのメッセージの受信 (02:13)。

フロントエンドの更新が最も直接的です。ユーザーがアプリを開いたり、選択を変更したり、アプリがフォアグラウンドで新しいデータを受信したりすると、コードは CLKComplicationServer.sharedInstance().reloadTimeline(for:) を呼び出し、ClockKit が現在のタイムライン エントリを要求します。ここでは選択的に更新し、本当に変更する必要がある追加機能のみを更新する必要があります (03:11)。

バックグラウンド更新メカニズムは、データ ソースに応じて選択する必要があります。バックグラウンド App Refresh は、HealthKit などの Watch 上の API やローカル データにアクセスするのに適しています。バックグラウンド URLSession は、サーバーからデータをダウンロードするのに適しています。コンプリケーション プッシュは、友人から送られた新しい励ましなど、イベント駆動型のデータに適しています。 3 つのメカニズムはすべて、アプリが非アクティブなときにアプリをウェイクアップできますが、それぞれに頻度、時間、タスクの完了に関する制約があります (01:33)。

##詳細

フロント デスクは reloadTimeline を使用して、現在のエントリの再構築をトリガーします。

(03:32) updateActiveComplications() は現在アクティブな追加機能を取得し、ClockKit にタイムラインを 1 つずつリロードするように要求します。

class ExtensionDelegate: NSObject, WKExtensionDelegate {

    func updateActiveComplications() {

        let complicationServer = CLKComplicationServer.sharedInstance()

        if let activeComplications = complicationServer.activeComplications {

            for complication in activeComplications {

                complicationServer.reloadTimeline(for: complication)

            }
        }
    }
}

キーポイント:

  • CLKComplicationServer.sharedInstance() は、ClockKit タイムラインを更新するためのエントリ ポイントです。
  • activeComplications には、現在のウォッチフェイスで実際に有効になっている合併症のみが含まれます。
  • サンプル コードは、すべてのアクティブな追加機能を更新します。この講義は、実際のアプリをより詳細にし、更新する必要がある項目のみを更新する必要があることを思い出させます。
  • reloadTimeline(for:) の後、ClockKit はデータ ソースから現在のエントリを要求します。

(04:26) ClockKit が現在のエントリをリクエストすると、アプリは追加機能ファミリーに基づいて対応するテンプレートを作成し、ハンドラーを通じて CLKComplicationTimelineEntry を返します。

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)
    }
}

キーポイント:

  • complication.family はどのテンプレートを返すかを決定します。
  • テンプレートは、テキスト プロバイダーなどのコンテンツ プロバイダーによって入力されます。
  • CLKComplicationTimelineEntry(date:complicationTemplate:) は、有効時間と表示テンプレートを結合します。
  • handler(entry) は完了する必要があるコールバックで、ClockKit は表示するコンテンツを取得します。

バックグラウンド アプリ更新はローカル データと Watch API に適しています

(05:20) サンプル アプリは、バックグラウンド アプリ更新を使用して HealthKit アクティビティ データを取得します。講演で示された頻度制限は 1 時間あたり 4 回までで、実際の回数は他のプロセスやバッテリー残量の影響を受けます。

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)")
        }
    }
}

キーポイント:

  • 「最初に?」 60 : 15*60 ` は、初回はできるだけ早くスケジュールし、その後は 15 分ペースでのリクエストに対応します。
  • userInfo は、スケジューリング中にコンテキストをもたらすことができます。この例では、「submissionDate」を使用してシステムの実際のウェイクアップ遅延を計算します。
  • scheduleBackgroundRefresh(withPreferredDate:userInfo:) の時刻が優先時刻であり、システムはこの時刻以降に起動する適切な時刻を選択することのみを保証します。
  • 完了ハンドラーはメインスレッドで非同期に呼び出され、スケジュールが失敗した場合はエラーを処理する必要があります。

(08:47) バックグラウンド作業が非同期の場合は、データプロバイダーが完了するまで待ってから、コンプリケーションを更新し、次の更新をスケジュールし、タスクを完了としてマークする必要があります。

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)
                }

キーポイント:

  • WKApplicationRefreshBackgroundTask は、Background App Refresh が到着したときのタスク タイプです。
  • healthDataProvider.refresh() は、HealthKit データを非同期的に読み取る役割を果たします。
  • データ変更がない場合に更新機会を無駄にしないように、「update」が true の場合にのみ「updateActiveComplications()」を呼び出します。
  • scheduleBAR(first: false) は、同時に未処理のリクエストが 1 つだけであるため、現在のタスクが完了する前に次のリクエストをスケジュールします。
  • すべての作業が完了した後に setTaskCompletedWithSnapshot(false) を呼び出す必要があります。講演では、合計制限時間 15 秒を超える一般的な理由は、完了のマークを付け忘れることであると指摘しています。

App のバックグラウンド更新ではネットワーク リクエストを行うことができません。この講演では、URLSession は例外であり、それを使用しようとすると失敗すると明示的に述べられています。また、2 つの時間制限も設けられています。アクティブな CPU 時間は最大 4 秒、合計時間は最大 15 秒です (09:22)。

サーバーデータに適したバックグラウンド URLSession

(11:35) 気象データはサーバーから取得されるため、サンプル アプリでは代わりにバックグラウンド URLSession を使用します。重要な設定は、バックグラウンド構成、非自由裁量、および「sessionSendsLaunchEvents = true」です。

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)
    }()

キーポイント:

  • URLSessionConfiguration.background(withIdentifier:) はバックグラウンド ダウンロード セッションを作成します。
  • isDiscretionary = false は、無料延期の要求がシステムに与えられないことを意味します。
  • sessionSendsLaunchEvents = true により、ダウンロード イベントが到着したときにアプリがバックグラウンドで起動できるようになります。
  • delegateQueue: nil により、URLSession デリゲート コールバックがバックグラウンド シリアル キューに入ります。

(12:02) ダウンロード タスクは、「earliestBeginDate」を介して最も早い開始時間を指定し、送受信される予想バイト数を設定します。

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
        }
    }
}

キーポイント:

  • backgroundTask == nil により、この例では同時に 1 つの天気予報ダウンロード タスクのみが保持されるようになります。
  • URL は最新のキャッシュの場所から取得され、音声シナリオは場所ごとの風速と天気を取得することです。
  • earliestBeginDate は、最初の 60 秒とその後の 15 分間のスケジュール ポリシーを再利用します。
  • countOfBytesClientExpectsToSendcountOfBytesClientExpectsToReceive は、システムが転送サイズを理解するのに役立ちます。
  • 「resume()」の後、ダウンロードはアプリとは独立して実行できます。完了すると、システムは「WKURLSessionRefreshBackgroundTask」を通じてアプリを起動します。

(14:08) ダウンロードが完了すると、デリゲートはファイル URL からデータを読み取り、JSON を処理します。次に、didCompleteWithError がメインスレッド上で保存された完了ハンドラーを呼び出します。

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)")
            }
        }
    }

キーポイント:

  • didFinishDownloadingTo は、ローカルエリアにダウンロードされたファイルの場所を受け取ります。
  • この例では、最初に location.isFileURL を確認し、次に Data(contentsOf:) を読み取ります。
  • KiteFlyingWeather(jsonData) はサンプルアプリの天気解析エントリです。
  • タスクが完了した後も、WKURLSessionRefreshBackgroundTask を完了としてマークする前に、URLSession デリゲート イベントが配信されるのを待つ必要があります。

また、バックグラウンドの URLSession は通常、1 時間あたり最大 4 つのリクエストを送受信できますが、実際の数は Wi-Fi、携帯電話の信号、バッテリー電力の影響を受けます。アプリがアクティブ化されたら、保留中のデリゲート コールバックを受信できるように、セッションに再接続する必要があります (10:31)。

合併症プッシュはイベント駆動型データに適しています

(16:16) データがサーバー上の緊急事態からのものである場合は、スケジュールされたプルよりもプッシュの方が適切です。サンプル アプリでは、コンプリケーション プッシュを使用して、友人からの励ましのメッセージを更新します。

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()
    }
}

キーポイント:

  • PKPushRegistry(queue: .main) は、PushKit コールバックがメイン キューを使用することを指定します。
  • desiredPushTypes = [.complication] は、コンプリケーションのプッシュ タイプに対応します。
  • didUpdate PushCredentials によって返された認証情報はサーバーにアップロードされます。
  • プッシュを受信した後、最初にペイロードを処理し、次に updateActiveComplications() を呼び出します。
  • completion() は、ペイロードの処理が完了した後に呼び出す必要があります。

サーバー側にも厳しい要件があります。アプリ識別子は .watchkitapp.complication で終わり、それを使用して Apple プッシュ通知サービス SSL 証明書を作成する必要があります。 WatchKit 拡張機能には、リモート通知バックグラウンド モードとプッシュ通知機能も必要です (16:51)。

各ウォッチは 1 日あたり最大 50 回の合併症プッシュを受け取ることができます。 51 番目以降の通知は無視されるため、サーバーはユーザーおよびデバイスごとにスロットルを調整する必要があります。プッシュ ペイロードはバックグラウンド プッシュとして編成され、「aps」辞書に「content-available」が含まれています (18:34)。

重要ポイント

  • アクティビティ進行状況コンプリケーション: 何をするか: その日のアクティビティステータスを文字盤に表示します。実行する価値がある理由: セッションの例では、アプリのバックグラウンド更新を使用して HealthKit を読み取り、バックグラウンド タスクは 1 時間あたり最大 4 回実行できます。開始方法: HealthKit クエリを HealthDataProvider.refresh() に置き、データが変更された場合にのみ updateActiveComplications() を呼び出します。
  • 風天気コンプリケーション: 機能: 最新のキャッシュされた位置に基づいて、凧揚げに適した風速と天気を表示します。実行する価値がある理由: セッションの例では、アプリが非アクティブなときにバックグラウンド URLSession を使用してサーバー データをダウンロードします。開始方法: 「URLSessionConfiguration.background」を作成し、「sessionSendsLaunchEvents = true」を設定し、JSON を解析して、ダウンロードの完了後にタイムラインを更新します。
  • 友達の励まし機能: 操作方法: 友達からの最新の励ましを文字盤に表示します。実行する価値がある理由: セッションは、イベント駆動型データが合併症のプッシュに適しており、緊急に応じてプッシュを送信できることを示しています。開始方法: PKPushRegistry.complication タイプを登録し、プッシュ認証情報をサーバーにアップロードし、ペイロードの受信後に関連する追加機能を更新します。
  • 設定変更の即時プレビュー: 対処方法: ユーザーが Watch アプリで場所、ターゲット、または表示項目を変更すると、ウォッチフェイスに新しいコンテンツがすぐに表示されます。実行する価値がある理由: アプリがフォアグラウンドでアクティブで、状態が入力の変更に応答するときに追加機能を更新するには、セッションを使用することをお勧めします。開始方法: 設定が保存された後に activeComplications を取得し、影響を受ける合併症に対してのみ reloadTimeline(for:) を呼び出します。

関連セッション

コメント

GitHub Issues · utterances