Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

⾃⼰紹介 ‣ Go Takagi • ⽇本経済新聞社 社会⼈2年⽬ • ⽇経電⼦版 / 紙⾯ビューアー / Nikkei Wave iOSアプリ開発 • Twitter: @shimastriper • 柴⽝とAppStoreのストーリーを毎⽇読むのが好き

Slide 3

Slide 3 text

⾃動ダウンロードするためのアジェンダ ‣ 紙⾯ビューアーへ新聞データを快適に届けたい ‣ iOSのBackground、制限厳しい ‣ 全体的なアーキテクチャ ‣ 端末だけが問題じゃないぞ!サーバー側へ ‣ やっぱり端末、そして性能差...... ‣ "安定して" 届けるための対策を振り返る 電⼦版広報⽝デンシバ

Slide 4

Slide 4 text

コンテンツの⾃動ダウンロード ‣ 快適なユーザー体験 • アプリを開いたユーザーが待たずに利⽤できる • ユーザーの定期的な⾏動意欲にも繋がる ‣ 通信への配慮 • 重いコンテンツをWi-Fi時にダウンロード • 実際に使うときはオフラインでも最新の状態にしておける

Slide 5

Slide 5 text

紙⾯ビューアー: 新聞紙⾯の画像ビューアー ‣ 紙⾯ビューアー • 紙ベースで新聞を読める • iPadユーザーが多い ‣ 快適なオフライン動作の需要 • Wi-Fiで⼀括DLして通信量節約 • iPadはCellularがついていないモデルも多い • 出かける前にDLしておく必要性 朝⼣刊‧他媒体を紙で読める

Slide 6

Slide 6 text

新聞の紙⾯データは定期配信コンテンツ ‣ 寝てる間にアプリへ配達 • 通勤で利⽤するユーザーがDLで億劫にならない • 朝まるで郵便受けに新聞が届いているように... ‣ ユーザーを逃さない • ⾃動DLを有効にするというのはポジティブな証拠 • ユーザーへの強いリテンションにも貢献する • ユーザーからの要望も⾮常に多かった

Slide 7

Slide 7 text

紙⾯画像データの通信量はそれなりのサイズ ‣ 画像容量はかさむ • ⽂字の可読性 ‣ 枚数も多い • 最⼤ページ数は48P ‣ 3段階の画質 • ⼩‧中‧⼤ ‣ 合計30MB〜 MB (WebP)

Slide 8

Slide 8 text

iOSのBackground、制限厳しい

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Background Modeの制約 ‣ APIごとに様々な制限が存在する • Power : バッテリーへの考慮 • Performance : メインアプリとのバランス • Privacy : 何をしているかユーザーに明⽰する ‣ 例えばどんな制限があるか? • 制限時間‧なるべく早く終える • 端末の状況で実⾏されないときもある • ユーザーからの許諾が必須 「バックグラウンド更新」の 許可も基本必要 https://developer.apple.com/videos/play/wwdc / /

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Backgroundでダウンロードする⽅法 ‣ Background App Refresh Task ( iOS . + ) • Background Fetch ( iOS . - . ) の後継 ‣ Background Notification ( iOS . + ) ‣ Background Transfer Service ( iOS . + ) ‣ Background Processing Task ( iOS . + )

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

BackgroundでコンテンツをRefreshする⽅法 ‣ Background App Refresh Task ( iOS . + ) • Background Fetch ( iOS . - . ) の後継 ‣ Background Notification ( iOS . + ) ‣ Background Transfer Service ( iOS . + ) ‣ Background Processing Task ( iOS . + ) ← 採⽤ 最新OS限定、検討もせず

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Background Notificationで実装した理由 2 ‣ 通信環境の考慮 • Wi-Fi + Cellularもサポートしたかった • Androidと仕様を揃える • ユーザーに選択できるようにしたい ‣ ビューアーは iOS までサポート (今はiOS ) • Background FetchはiOS 以降で分岐対応 • 挙動が⼤きく違ったら担保できるか?という懸念 Android版はCellularも対応

Slide 20

Slide 20 text

全体的なアーキテクチャ

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

サーバー側アーキテクチャ〜アプリへ届くまで〜 ② 定期的にDL可能な 今⽇の新聞リストを抽出 朝刊 + 他媒体... "84-BNCEB 'JSFCBTF$MPVE.FTTBHJOH 碊ꪫلٖ٭ؓ٭ "NB[PO4 デバイスに最適化した 紙⾯画像が随時プール

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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をチェック

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

成功時にローカル通知を発⾏ DL済みの媒体が表⽰される DLして、ローカル通知 (⾒出しはサンプルです)

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Background Notificationの性質に対処 ‣ 実⾏確率を上げるため、複数回送る (基本1回では⾜りない) • 実⾏済みならnoDataを通知する • データが重いならDLを中断‧再開できる仕組みにする • それかBackground Transferにリクエストする ‣ Background Notification⾃体を複数種類作らない • 受信‧実⾏が特定のものに偏るおそれ • あくまで実⾏後に実際どの処理をさせるか分岐するといい • 例えば、APNSのPayloadにやりたいことの優先順位をつけておく

Slide 31

Slide 31 text

OS側が⽌めてしまうことを前提としたロジック ‣ 排他制御のミスに気をつける • Lockをかけて⾮同期処理のcompletedを待って解除するとか • OSによって強制終了されると⼀⽣ロックされてしまう ‣ 最⼩限の単純なロジックで動作するようにする • 時間もメモリも⼤切に、いらないリクエストをしない • サーバー側で解決できることは寄せておいたほうがいい • (やっぱりデバッグも⼤変なので......)

Slide 32

Slide 32 text

提⽰できる状態はなるべく明⽰的に周知 ‣ 実⾏するタイミングが不定だからこそ重要 • ユーザーにとっては何が原因で動かないのかよくわからない ‣ ユーザーによる意図的なアプリの終了 • Background Notificationも受け取らなくなる • 反射的に終了されないように説明する

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

「設定アプリ」の許諾状態の表⽰ ‣ アプリに対する許諾を取得することができる • UIBackgroundRefreshStatus (UIKit) ( iOS . + ) • 「Appのバックグラウンド更新」の可否 • UNNotificationSettings (UNNotificationCenter) ( iOS . + ) • 「通知」の可否

Slide 35

Slide 35 text

(許諾が⾜りなければ) アプリ上でも明⽰的に伝える

Slide 36

Slide 36 text

Background Push Notificationの動作確認 ‣ 通知が必要なので、基本的に実機が必須 • Xcode . からSimulatorで操作できる機能がついた (が!!次ページ!!) ‣ Foregroundで受信 • 確実に動く ‣ (Xcodeの) DebuggerをつなげたままBackgroundで受信 • アプリが停⽌状態にならないため⾼確率で実⾏ ‣ Wi-Fi + 充電したままBackgroundで受信 • OSの判断で状況により実⾏しない、時間制限もある ‣ 環境を厳しくしていくと....... • より実⾏機会が減っていく

Slide 37

Slide 37 text

(ちなみに) 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" : , "aps" : { "alert": "Drag & Dropするだけ", "badge": } } { "Simulator Target Bundle" : , "aps" : { "content-available": } }

Slide 38

Slide 38 text

よし、完成!? 端末だけが問題じゃないぞ!サーバー側へ

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

運⽤すると課題が⾒えてくる ‣ サーバーアクセスのピークへの対処 • 元々通知がないアプリだったため、対処が⾜りてなかった ‣ どういう対策を⼊れるか • 適当にタイマーを発⽣させて遅延させる (Android) • 30秒以下という時間制限だとiOSでは厳しい • Requestの徹底整理 • CDN‧APIサーバー、適切にキャッシュをもたせる • ユーザーデータなどのキャッシュが難しいものへの対処‧削除 • それでもダメなら.... → 受信対象群を分割

Slide 41

Slide 41 text

CDNで捌いて負荷対策

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

端末による成功率の違いが如実に ‣ Cellular通信時、Notification起動率が⼤きく違う • iPhone XまではCellularでも安定実⾏ • iPhone s, iPad Airなどは3⽇ほど放置して⼀切受信しないことも ‣ 当時: iOS サポート • 古い端末が⼀定数利⽤されていることが確認されている • 機能として安定しないものを公開するのは...... ‣ 結論 • ⼀旦Cellularは無しでWi-Fiのみでリリースしました (⾟い)

Slide 44

Slide 44 text

実⾏チャンスを増やすには...... ‣ 他のBackground Modeも同時併⽤する • BGTask‧Background Transfer ‣ Notification Service Extensionは確実に実⾏する • 本来、アラート通知の情報を追加取得 (サムネイルなど)するための処理 • (通知は必ず表⽰するため) DLが終わらなければ「失敗しました」と表⽰ ‣ LocationAPIでアプリを起こして受信確率を上げる • もちろん元々位置情報を使う真っ当な理由が必須 • 限られたアプリでしか適⽤できない

Slide 45

Slide 45 text

OSによって落とされにくいアプリへと改善する ‣ 逆に考えよう • 起こすのが⼤変なら落とされにくいアプリを⽬指せばいい! ‣ OSによってメモリから落とされる原因として推察できること • メモリ⾷い過ぎ • 重い他メインアプリの動作を優先 • バグがあってひっそりクラッシュ...... • MetricKitがアップデートしてBackground Killを特定できそう! • ユーザーによる⼿動Killすら拾えるみたい • 2020のリンクを載せておくので詳細は動画を各⾃で アプリ本体の改善 https://developer.apple.com/videos/play/wwdc / /

Slide 46

Slide 46 text

"安定して" 届けるための対策を振り返る

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

安定して受け取るために...... ‣ 利⽤するBackground Modeの選択 • コンテンツの性質は?それに合わせたAPIは? 配信タイミングでBackground Notification ‣ 動かしたい時間帯の考察 • 通信環境? Wi-Fi (いずれ Cellular も...) • 充電環境? 朝⽅は多くの端末は充電してるはず • 他のアプリは動いている? 多くの端末では動いてない時間帯 • 他のBackground処理は動いてそう? AM : のようなキリのいい時間は避ける? (恣意的ですが) ‣ アプリ本体は健康? • なるべくOSから落とされにくい状態を堅持 • 使⽤メモリやバグ⼤丈夫? (サーバーログからはOSによる遅延調整はあまり無かった) iOS でもっとやるぞ!! ⽇経の場合は

Slide 49

Slide 49 text

まとめ ‣ BackgroundNotificationで新聞を毎朝届けられた! • とはいえ安定して成功するのは⼤変 • Background時の処理は必要最⼩限にしましょう • サーバー側で解決できるものはしておく • 重い場合、中断‧再開できる仕組みにして保険をかける • アクセスが集中するのでサーバー側の負荷チェックを忘れずに • アプリ全体の振る舞いを最適化して実⾏機会を増やす • 通信‧電源環境は特に⼤変、⾃社がサポートする端末スペックを要確認

Slide 50

Slide 50 text

[参考] セッション動画 (おすすめ視聴順) 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 ,

Slide 51

Slide 51 text

[参考] ドキュメント 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/