Background Notificationで新聞紙面の大きい画像の自動ダウンロードを実現する

9bf153d8c0ce36ba6d9d20c2914b70f4?s=47 Go Takagi
September 20, 2020

Background Notificationで新聞紙面の大きい画像の自動ダウンロードを実現する

iOSDC2020 Day 1 Track A 10:50〜
Background Notificationで新聞紙面の大きい画像の自動ダウンロードを実現する

本セッションでは、大きい画像コンテンツを対象にした自動ダウンロード機能について説明します。
実装の過程で直面した問題への対策やアプリやサーバーサイドを含めた全体のアーキテクチャを解説するとともに、日本経済新聞 紙面ビューアーにおける新聞紙面画像での運用事例を紹介します。
大規模サービスにおける運用を通じて得られた安定して自動ダウンロードを成功させるための課題の解決方法を説明します。

https://www.youtube.com/watch?v=KI8sGk6hhNU
https://fortee.jp/iosdc-japan-2020/proposal/6bb15eb4-09f9-4fb9-b451-ae8fbd530790
https://iosdc.jp/2020/

9bf153d8c0ce36ba6d9d20c2914b70f4?s=128

Go Takagi

September 20, 2020
Tweet

Transcript

  1. Background Notificationで 新聞紙⾯の⼤きい画像の ⾃動ダウンロードを実現する Go Takagi (@shimastriper) iOSDC JAPAN Day

    #Track A : 〜
  2. ⾃⼰紹介 ‣ Go Takagi • ⽇本経済新聞社 社会⼈2年⽬ • ⽇経電⼦版 /

    紙⾯ビューアー / Nikkei Wave iOSアプリ開発 • Twitter: @shimastriper • 柴⽝とAppStoreのストーリーを毎⽇読むのが好き
  3. ⾃動ダウンロードするためのアジェンダ ‣ 紙⾯ビューアーへ新聞データを快適に届けたい ‣ iOSのBackground、制限厳しい ‣ 全体的なアーキテクチャ ‣ 端末だけが問題じゃないぞ!サーバー側へ ‣

    やっぱり端末、そして性能差...... ‣ "安定して" 届けるための対策を振り返る 電⼦版広報⽝デンシバ
  4. コンテンツの⾃動ダウンロード ‣ 快適なユーザー体験 • アプリを開いたユーザーが待たずに利⽤できる • ユーザーの定期的な⾏動意欲にも繋がる ‣ 通信への配慮 •

    重いコンテンツをWi-Fi時にダウンロード • 実際に使うときはオフラインでも最新の状態にしておける
  5. 紙⾯ビューアー: 新聞紙⾯の画像ビューアー ‣ 紙⾯ビューアー • 紙ベースで新聞を読める • iPadユーザーが多い ‣ 快適なオフライン動作の需要

    • Wi-Fiで⼀括DLして通信量節約 • iPadはCellularがついていないモデルも多い • 出かける前にDLしておく必要性 朝⼣刊‧他媒体を紙で読める
  6. 新聞の紙⾯データは定期配信コンテンツ ‣ 寝てる間にアプリへ配達 • 通勤で利⽤するユーザーがDLで億劫にならない • 朝まるで郵便受けに新聞が届いているように... ‣ ユーザーを逃さない •

    ⾃動DLを有効にするというのはポジティブな証拠 • ユーザーへの強いリテンションにも貢献する • ユーザーからの要望も⾮常に多かった
  7. 紙⾯画像データの通信量はそれなりのサイズ ‣ 画像容量はかさむ • ⽂字の可読性 ‣ 枚数も多い • 最⼤ページ数は48P ‣

    3段階の画質 • ⼩‧中‧⼤ ‣ 合計30MB〜 MB (WebP)
  8. iOSのBackground、制限厳しい

  9. Background Mode ‣ アプリのバックグラウンド実⾏ • Foregroundにいない状態でもアプリ‧コードを実⾏できる • アプリからのリクエストや特定のイベントをトリガーに実⾏ ‣ iOSでは⽤途に応じて利⽤できるAPIが存在

    • 位置情報の取得 • Bluetooth通信 • サーバーと同期‧モデルの訓練 • コンテンツのダウンロード‧アップデート • etc... https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app 'PSFHSPVOE
  10. Background Modeの制約 ‣ APIごとに様々な制限が存在する • Power : バッテリーへの考慮 • Performance

    : メインアプリとのバランス • Privacy : 何をしているかユーザーに明⽰する ‣ 例えばどんな制限があるか? • 制限時間‧なるべく早く終える • 端末の状況で実⾏されないときもある • ユーザーからの許諾が必須 「バックグラウンド更新」の 許可も基本必要 https://developer.apple.com/videos/play/wwdc / /
  11. Background Mode ‣ アプリのバックグラウンド実⾏ • Foregroundにいない状態でもアプリ‧コードを実⾏できる • アプリからのリクエストや特定のイベントをトリガーに実⾏ ‣ iOSでは⽤途に応じて利⽤できるAPIが存在

    • 位置情報の取得 • Bluetooth通信 • サーバーと同期‧モデルの訓練 • コンテンツのダウンロード‧アップデート • etc... https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app 'PSFHSPVOE
  12. Backgroundでダウンロードする⽅法 ‣ Background App Refresh Task ( iOS . +

    ) • Background Fetch ( iOS . - . ) の後継 ‣ Background Notification ( iOS . + ) ‣ Background Transfer Service ( iOS . + ) ‣ Background Processing Task ( iOS . + )
  13. Background App Refresh Task ( iOS . + ) ‣

    特徴 • Background Fetch ( iOS . - . ) の後継 • OSが判断して定期的に実⾏する • App Refresh Taskは都度リクエスト • 30秒の実⾏時間 • 実⾏間隔を指定 • 使⽤頻度に応じて実際の間隔は増減 • 1⽇を通してアプリを最新に保てる この例はユーザーが使う直前に実⾏ ⼀定間隔の定期Refresh https://developer.apple.com/videos/play/wwdc / /
  14. Background Notification ( iOS . + ) ‣ 特徴 •

    サーバー経由で通知を送信して実⾏ • 通知の許諾も必要ない • 受信時にOSが判断して実⾏ • 30秒の実⾏時間 • Foreground時は必ず実⾏ • Background時はバッテリー‧通信状況に応じて実⾏ • ⽬安として、多くても1時間に2,3回に留める 明⽰的なRefresh要求を投げれる 特定タイミングのRefresh https://developer.apple.com/videos/play/wwdc / /
  15. Background Transfer Service ( iOS . + ) ‣ 特徴

    • URLSessionのBackground mode • ⼤きなファイル向けの通信 • アプリと別プロセスでOSが実⾏ • 時間制限も基本ない • Background Push経由でリクエストも可能 • isDiscretionaryプロパティで遅延リクエストをする • OSが適切なタイミングで実⾏させる (Wi-Fiや充電時) • Backgroundで呼ばれると強制 isDiscretionary ⻑めのDL/ULを遅延予約 遅延して適切なタイミングでDL https://developer.apple.com/videos/play/wwdc / /
  16. BackgroundでコンテンツをRefreshする⽅法 ‣ Background App Refresh Task ( iOS . +

    ) • Background Fetch ( iOS . - . ) の後継 ‣ Background Notification ( iOS . + ) ‣ Background Transfer Service ( iOS . + ) ‣ Background Processing Task ( iOS . + ) ← 採⽤ 最新OS限定、検討もせず
  17. Background Notificationで実装した理由 1 ‣ コンテンツとの相性 (朝⼣刊) • 配信タイミングで実⾏がBEST • 移動前に動いてほしい

    • ユーザーの⾏動に依らない • 定期的にRefreshするものでない • ⼣刊を夜中にDLしても意味無し                         鵟ⳝ 䅯㱝 消洣 ⩗꧅ ☼◄
  18. Background Notificationで実装した理由 1 ‣ コンテンツとの相性 (朝⼣刊) • 配信タイミングで実⾏がBEST • 移動前に動いてほしい

    • ユーザーの⾏動に依らない • 定期的にRefreshするものでない • ⼣刊を夜中にDLしても意味無し                         鵟ⳝ 䅯㱝 僼⮍ ☽㨥⛮ 㛗⮍ 消洣 ⩗꧅ ☼◄ 実⾏したいタイミングは 2つの時間帯に集中
  19. Background Notificationで実装した理由 2 ‣ 通信環境の考慮 • Wi-Fi + Cellularもサポートしたかった •

    Androidと仕様を揃える • ユーザーに選択できるようにしたい ‣ ビューアーは iOS までサポート (今はiOS ) • Background FetchはiOS 以降で分岐対応 • 挙動が⼤きく違ったら担保できるか?という懸念 Android版はCellularも対応
  20. 全体的なアーキテクチャ

  21. サーバー側アーキテクチャ〜アプリへ届くまで〜 "84-BNCEB 'JSFCBTF$MPVE.FTTBHJOH 碊ꪫلٖ٭ؓ٭ "NB[PO4

  22. サーバー側アーキテクチャ〜アプリへ届くまで〜 ① デバイスに最適化した 紙⾯画像が随時プール "84-BNCEB 'JSFCBTF$MPVE.FTTBHJOH 碊ꪫلٖ٭ؓ٭ "NB[PO4

  23. サーバー側アーキテクチャ〜アプリへ届くまで〜 ② 定期的にDL可能な 今⽇の新聞リストを抽出 朝刊 + 他媒体... "84-BNCEB 'JSFCBTF$MPVE.FTTBHJOH 碊ꪫلٖ٭ؓ٭

    "NB[PO4 デバイスに最適化した 紙⾯画像が随時プール
  24. サーバー側アーキテクチャ〜アプリへ届くまで〜 Background Push ③ 朝刊 (+α) OK!! 定期的にDL可能な 今⽇の新聞リストを抽出 朝刊

    + 他媒体... "84-BNCEB 'JSFCBTF$MPVE.FTTBHJOH 碊ꪫلٖ٭ؓ٭ "NB[PO4 デバイスに最適化した 紙⾯画像が随時プール
  25. サーバー側アーキテクチャ〜アプリへ届くまで〜 アカウントがDL可能な 媒体をダウンロード ④ Background Push 朝刊 (+α) OK!! 定期的にDL可能な

    今⽇の新聞リストを抽出 "84-BNCEB 'JSFCBTF$MPVE.FTTBHJOH 碊ꪫلٖ٭ؓ٭ "NB[PO4 デバイスに最適化した 紙⾯画像が随時プール
  26. Background Notificationの送り⽅ (APNS) ‣ Header (iOS から必須) ‣ Body apns-priority

    = apns-push-type = background { "aps" : { "content-available" : }, "acme " : "bar", "acme " : } https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app Capabilitiesをチェック
  27. Background Notification受信時の動作 ‣ なるべく早くcompletionHandlerを呼ぶ • newData / failed / noData

    のステータスをつけて終わらせる • OSが設定した時間(Max 秒)を超えても強制的に終了 func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { URLSession.shared.dataTask(with: url) { data, response, error in completionHandler(.newData) }.resume() }
  28. 成功時にローカル通知を発⾏ DL済みの媒体が表⽰される DLして、ローカル通知 (⾒出しはサンプルです)

  29. 細かいTips 五⽉⾬に話します!

  30. Background Notificationの性質に対処 ‣ 実⾏確率を上げるため、複数回送る (基本1回では⾜りない) • 実⾏済みならnoDataを通知する • データが重いならDLを中断‧再開できる仕組みにする •

    それかBackground Transferにリクエストする ‣ Background Notification⾃体を複数種類作らない • 受信‧実⾏が特定のものに偏るおそれ • あくまで実⾏後に実際どの処理をさせるか分岐するといい • 例えば、APNSのPayloadにやりたいことの優先順位をつけておく
  31. OS側が⽌めてしまうことを前提としたロジック ‣ 排他制御のミスに気をつける • Lockをかけて⾮同期処理のcompletedを待って解除するとか • OSによって強制終了されると⼀⽣ロックされてしまう ‣ 最⼩限の単純なロジックで動作するようにする •

    時間もメモリも⼤切に、いらないリクエストをしない • サーバー側で解決できることは寄せておいたほうがいい • (やっぱりデバッグも⼤変なので......)
  32. 提⽰できる状態はなるべく明⽰的に周知 ‣ 実⾏するタイミングが不定だからこそ重要 • ユーザーにとっては何が原因で動かないのかよくわからない ‣ ユーザーによる意図的なアプリの終了 • Background Notificationも受け取らなくなる

    • 反射的に終了されないように説明する
  33. Background Notificationに通知の許諾はいらない ‣ background notification • UIApplication.registerForRemoteNotifications() でdeviceTokenは取得可能 • UNUserNotificationCenter.requestAuthorization()

    は不要 • 受信時もapplication関数にdelegateされる ‣ alert notification (⼀般的な通知の⽅) • UserNotifications.framework ( iOS + ) に集約されてる • UNUserNotificationCenter • UNUserNotificationCenterDelegate 完了後のローカル通知には必要 https://developer.apple.com/videos/play/wwdc / /
  34. 「設定アプリ」の許諾状態の表⽰ ‣ アプリに対する許諾を取得することができる • UIBackgroundRefreshStatus (UIKit) ( iOS . +

    ) • 「Appのバックグラウンド更新」の可否 • UNNotificationSettings (UNNotificationCenter) ( iOS . + ) • 「通知」の可否
  35. (許諾が⾜りなければ) アプリ上でも明⽰的に伝える

  36. Background Push Notificationの動作確認 ‣ 通知が必要なので、基本的に実機が必須 • Xcode . からSimulatorで操作できる機能がついた (が!!次ページ!!)

    ‣ Foregroundで受信 • 確実に動く ‣ (Xcodeの) DebuggerをつなげたままBackgroundで受信 • アプリが停⽌状態にならないため⾼確率で実⾏ ‣ Wi-Fi + 充電したままBackgroundで受信 • OSの判断で状況により実⾏しない、時間制限もある ‣ 環境を厳しくしていくと....... • より実⾏機会が減っていく
  37. (ちなみに) Simulatorで通知を操作する ‣ Xcode . から使⽤可能に! • SimulatorにDrag & Dropで通知が出せる

    !! ‣ ただし、BG Notificationはバグってる • BG Fetchで使う関数が⽴ち上がる... • application(_:performFetchWithCompletionHandler:) • Xcode . でも直っておらず Simulator supports simulating remote push notifications, including background content fetch notifications. https://developer.apple.com/documentation/xcode-release-notes/xcode- _ -release-notes https://developer.apple.com/forums/thread/ { "Simulator Target Bundle" : <BUNDLE_ID>, "aps" : { "alert": "Drag & Dropするだけ", "badge": } } { "Simulator Target Bundle" : <BUNDLE_ID>, "aps" : { "content-available": } }
  38. よし、完成!? 端末だけが問題じゃないぞ!サーバー側へ

  39. APIサーバーに瞬間的にリクエストが殺到 ⼀部公開でも⼤変な量に

  40. 運⽤すると課題が⾒えてくる ‣ サーバーアクセスのピークへの対処 • 元々通知がないアプリだったため、対処が⾜りてなかった ‣ どういう対策を⼊れるか • 適当にタイマーを発⽣させて遅延させる (Android)

    • 30秒以下という時間制限だとiOSでは厳しい • Requestの徹底整理 • CDN‧APIサーバー、適切にキャッシュをもたせる • ユーザーデータなどのキャッシュが難しいものへの対処‧削除 • それでもダメなら.... → 受信対象群を分割
  41. CDNで捌いて負荷対策

  42. やっぱり端末、そして性能差......

  43. 端末による成功率の違いが如実に ‣ Cellular通信時、Notification起動率が⼤きく違う • iPhone XまではCellularでも安定実⾏ • iPhone s, iPad

    Airなどは3⽇ほど放置して⼀切受信しないことも ‣ 当時: iOS サポート • 古い端末が⼀定数利⽤されていることが確認されている • 機能として安定しないものを公開するのは...... ‣ 結論 • ⼀旦Cellularは無しでWi-Fiのみでリリースしました (⾟い)
  44. 実⾏チャンスを増やすには...... ‣ 他のBackground Modeも同時併⽤する • BGTask‧Background Transfer ‣ Notification Service

    Extensionは確実に実⾏する • 本来、アラート通知の情報を追加取得 (サムネイルなど)するための処理 • (通知は必ず表⽰するため) DLが終わらなければ「失敗しました」と表⽰ ‣ LocationAPIでアプリを起こして受信確率を上げる • もちろん元々位置情報を使う真っ当な理由が必須 • 限られたアプリでしか適⽤できない
  45. OSによって落とされにくいアプリへと改善する ‣ 逆に考えよう • 起こすのが⼤変なら落とされにくいアプリを⽬指せばいい! ‣ OSによってメモリから落とされる原因として推察できること • メモリ⾷い過ぎ •

    重い他メインアプリの動作を優先 • バグがあってひっそりクラッシュ...... • MetricKitがアップデートしてBackground Killを特定できそう! • ユーザーによる⼿動Killすら拾えるみたい • 2020のリンクを載せておくので詳細は動画を各⾃で アプリ本体の改善 https://developer.apple.com/videos/play/wwdc / /
  46. "安定して" 届けるための対策を振り返る

  47. 安定して受け取るために...... ‣ 利⽤するBackground Modeの選択 • コンテンツの性質は?それに合わせたAPIは? ‣ 動かしたい時間帯の考察 • 通信環境?

    • 充電環境? • 他のアプリは動いている? • 他のBackground処理は動いてそう? ‣ アプリ本体は健康? • なるべくOSから落とされにくい状態を堅持 • 使⽤メモリやバグ⼤丈夫?
  48. 安定して受け取るために...... ‣ 利⽤するBackground Modeの選択 • コンテンツの性質は?それに合わせたAPIは? 配信タイミングでBackground Notification ‣ 動かしたい時間帯の考察

    • 通信環境? Wi-Fi (いずれ Cellular も...) • 充電環境? 朝⽅は多くの端末は充電してるはず • 他のアプリは動いている? 多くの端末では動いてない時間帯 • 他のBackground処理は動いてそう? AM : のようなキリのいい時間は避ける? (恣意的ですが) ‣ アプリ本体は健康? • なるべくOSから落とされにくい状態を堅持 • 使⽤メモリやバグ⼤丈夫? (サーバーログからはOSによる遅延調整はあまり無かった) iOS でもっとやるぞ!! ⽇経の場合は
  49. まとめ ‣ BackgroundNotificationで新聞を毎朝届けられた! • とはいえ安定して成功するのは⼤変 • Background時の処理は必要最⼩限にしましょう • サーバー側で解決できるものはしておく •

    重い場合、中断‧再開できる仕組みにして保険をかける • アクセスが集中するのでサーバー側の負荷チェックを忘れずに • アプリ全体の振る舞いを最適化して実⾏機会を増やす • 通信‧電源環境は特に⼤変、⾃社がサポートする端末スペックを要確認
  50. [参考] セッション動画 (おすすめ視聴順) The Push Notifications primer iOSの通知全体を通して学べる Advances in

    App Background Execution iOSのBackground execution全体の解説 Background execution demystified Background executionの実⾏条件のより細かい制約を紹介してくれている What's New with Multitasking iOS で⼊ったBackgroundでAppを更新するためのAPI群の主な解説 Why is my app getting killed? MetricKitを⽤いたAppのBackground Killの原因と特定⽅法の解説 WWDC , WWDC , WWDC , WWDC , WWDC ,
  51. [参考] ドキュメント Pushing Background Updates to Your App Background Notification

    https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app Downloading Files in the Background Background Transfer https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_in_the_background?language=objc 設定画⾯の情報取得 バックグラウンドの更新ステータス‧通知の許諾ステータス https://developer.apple.com/documentation/uikit/uiapplication/ -backgroundrefreshstatus https://developer.apple.com/documentation/usernotifications/unnotificationsettings Simulatorで通知をハンドリング Simulator上で操作する⼿順‧Background Notificationハンドリング時のバグらしき挙動の報告 https://developer.apple.com/documentation/xcode-release-notes/xcode- _ -release-notes https://developer.apple.com/forums/thread/