Upgrade to Pro — share decks privately, control downloads, hide ads and more …

サーバーと同期してリアルタイムに更新する画面を実装する

izumi
September 12, 2022

 サーバーと同期してリアルタイムに更新する画面を実装する

izumi

September 12, 2022
Tweet

More Decks by izumi

Other Decks in Programming

Transcript

  1. サーバーと同期してリアルタイム
    に更新する画面を実装する
    iOSDC2022 @rizumi

    View Slide

  2. Cluster, Inc. All Rights Reserved. 2
    自己紹介
    Ryo Izumi @rizumi_jp
    クラスター株式会社
    Software Engineer

    View Slide

  3. Cluster, Inc. All Rights Reserved. 3
    clusterの紹介

    View Slide

  4. Cluster, Inc. All Rights Reserved. 4
    outroom
    inroom

    View Slide

  5. 5
    アプリの画面更新

    View Slide

  6. Cluster, Inc. All Rights Reserved. 6
    アプリの画面更新
    DB
    API Server
    Client
    ● Serverからデータを取得して表示する画面

    View Slide

  7. Cluster, Inc. All Rights Reserved. 7
    アプリの画面更新
    DB
    API Server
    Client
    ● Serverからデータを取得して表示する画面
    fetch

    View Slide

  8. Cluster, Inc. All Rights Reserved. 8
    アプリの画面更新
    DB
    API Server
    Client
    response
    ● Serverからデータを取得して表示する画面
    fetch

    View Slide

  9. Cluster, Inc. All Rights Reserved. 9
    アプリの画面更新
    DB
    API Server
    Client
    response
    Update
    ● Serverからデータを取得して表示する画面
    fetch

    View Slide

  10. Cluster, Inc. All Rights Reserved. 10
    アプリの画面更新
    DB
    API Server
    Client
    反映したい
    response
    Update
    ● Serverからデータを取得して表示する画面
    fetch

    View Slide

  11. Cluster, Inc. All Rights Reserved. 11
    画面更新のパターン
    ● ユーザーの明示的なアクション
    ○ PullToRefreshや更新ボタン
    ● 自動的な更新
    ○ serverの更新を検知してリアルタイムな更新処理

    View Slide

  12. Cluster, Inc. All Rights Reserved. 12
    画面更新のパターン
    ● ユーザーの明示的なアクション
    ○ PullToRefreshや更新ボタン
    ● 自動的な更新
    ○ serverの更新を検知してリアルタイムな更新処理

    View Slide

  13. Cluster, Inc. All Rights Reserved. 13
    ※リアルタイムの定義
    ● 本セッションでの ”リアルタイム”は 基本的に
    soft real-timeの事を意味します
    ※ soft real-time:
    時間内に処理できなくても品質は下がるが価値は0にならない

    View Slide

  14. Cluster, Inc. All Rights Reserved. 14
    リアルタイム更新が必要な場合
    ● ユーザーに情報の更新をいち早く伝えたい場合
    ○ ex. チャット, ユーザーのオンライン状態
    ● 逆にリアルタイム更新すべきでないケースもある
    ○ ex. 商品詳細の説明文など

    View Slide

  15. 15
    リアルタイム更新の実装例

    View Slide

  16. Cluster, Inc. All Rights Reserved. 16
    更新通知の受信方法
    1. polling
    2. Push通知
    3. Firebase Realtime Database / Cloud Firestore
    4. WebSocket

    View Slide

  17. Cluster, Inc. All Rights Reserved. 17
    更新通知の受信方法
    1. polling
    2. Push通知
    3. Firebase Realtime Database / Cloud Firestore
    4. WebSocket

    View Slide

  18. Cluster, Inc. All Rights Reserved. 18
    更新方法1. polling
    let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
    Task {
    // 差分があれば画面更新
    let data = await self.apiClient.fetch()
    refreshIfNeeded(data)
    }
    }

    View Slide

  19. Cluster, Inc. All Rights Reserved. 19
    更新方法1. polling
    ● メリット
    ○ 実装がシンプルでAPIも使いまわせる
    ● デメリット
    ○ 無駄なAPI通信が発生するのでパフォーマンスが悪い

    View Slide

  20. Cluster, Inc. All Rights Reserved. 20
    更新通知の受信方法
    1. polling
    2. Push通知
    3. Firebase Realtime Database / Cloud Firestore
    4. WebSocket

    View Slide

  21. Cluster, Inc. All Rights Reserved. 21
    更新方法2. サイレントPush通知
    https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_serv
    er/pushing_background_updates_to_your_app

    View Slide

  22. Cluster, Inc. All Rights Reserved. 22
    更新方法2. サイレントPush通知
    ● Push通知のpayloadにcontent-available: 1を指定
    {
    "aps": {
    "content-available": 1
    },
    "data": {
    "example":"sync-notify"
    }
    }

    View Slide

  23. Cluster, Inc. All Rights Reserved. 23
    更新方法2. サイレントPush通知
    {
    "aps": {
    "content-available": 1
    },
    "data": {
    "example":"sync-notify"
    }
    }
    ● Push通知のpayloadにcontent-available: 1を指定
    任意の値を入れれる

    View Slide

  24. Cluster, Inc. All Rights Reserved. 24
    更新方法2. サイレントPush通知
    func application(_
    application: UIApplication,
    didReceiveRemoteNotification userInfo: [AnyHashable: Any],
    fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    // サイレントpush受信
    // ここで画面に受信通知をする (e.g. NotificationCenter)
    }
    ● Appdelegateで受信時の処理をハンドリング

    View Slide

  25. Cluster, Inc. All Rights Reserved. 25
    更新方法2. サイレントPush通知
    ● メリット
    ○ 既存のPushの仕組みを使いまわせる
    ● デメリット
    ○ 他Platformへの流用が難しい
    ○ ユーザー設定でOFFにできてしまう
    ○ Push通知を対応していない場合は対応が必要

    View Slide

  26. Cluster, Inc. All Rights Reserved. 26
    更新通知の受信方法
    1. polling
    2. Push通知
    3. Firebase Realtime Database / Cloud Firestore
    4. WebSocket

    View Slide

  27. Cluster, Inc. All Rights Reserved. 27
    更新方法3. Firebase Realtime Database / Cloud Firestore

    View Slide

  28. Cluster, Inc. All Rights Reserved. 28
    import FirebaseDatabase
    ...
    // Realtime Database
    let ref = Database.database().reference()
    ref.child(“users”).observe(.value, with: { snapshot in
    let value = snapshot.value
    // 画面更新
    refresh(value)
    })
    更新方法3. Firebase Realtime Database / Cloud Firestore

    View Slide

  29. Cluster, Inc. All Rights Reserved. 29
    ● メリット
    ○ client実装がシンプル
    ○ インフラのメンテナンスコスト低い
    ■ スケールなどをFirebaseに頼れる
    ● デメリット
    ○ FirebaseにDBが依存
    更新方法3. Firebase Realtime Database / Cloud Firestore

    View Slide

  30. Cluster, Inc. All Rights Reserved. 30
    更新通知の受信方法
    1. polling
    2. Push通知
    3. Firebase Realtime Database / Cloud Firestore
    4. WebSocket

    View Slide

  31. Cluster, Inc. All Rights Reserved. 31
    更新方法4. WebSocket
    Client Server
    Update通知
    Connection
    Close

    View Slide

  32. Cluster, Inc. All Rights Reserved. 32
    更新方法4. WebSocket
    class WebSocketEngine: NSObject, URLSessionWebSocketDelegate {
    private var task: URLSessionWebSocketTask?
    func connect() {
    let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
    task = session.webSocketTask(with: .init(string: "ws://example")!)
    doReceive()
    task?.resume()
    }
    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask,
    didOpenWithProtocol protocol: String?) {
    // connected
    }
    }

    View Slide

  33. Cluster, Inc. All Rights Reserved. 33
    更新方法4. WebSocket
    private func doReceive() {
    task?.receive { [weak self] (result) in
    switch result {
    case .success(let message):
    switch message {
    case .string(let string):
    // Receive string
    case .data(let data):
    // Receive data
    @unknown default:
    // error
    }
    self?.doReceive()
    case .failure(let error):
    // error
    }
    }
    }

    View Slide

  34. Cluster, Inc. All Rights Reserved. 34
    更新方法4. WebSocket
    func disconnect() {
    task?.cancel(with: .normalClosure, reason: nil)
    }
    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask,
    didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
    // disconnected
    }

    View Slide

  35. Cluster, Inc. All Rights Reserved. 35
    更新方法4. WebSocket
    SwiftのWebSocket library
    ● Starscream
    ○ https://github.com/daltoniam/Starscream
    ● socket.io-client-swift
    ○ https://github.com/socketio/socket.io-client-swift
    ● SwiftWebSocket
    ○ https://github.com/tidwall/SwiftWebSocket

    View Slide

  36. Cluster, Inc. All Rights Reserved. 36
    更新方法4. WebSocket
    ● メリット
    ○ 拡張性/自由度が高い
    ■ 他Platformでも同様の仕組みが使いまわせる
    ● デメリット
    ○ client/server共に実装が大変になりがち

    View Slide

  37. Cluster, Inc. All Rights Reserved. 37
    各手法のpros / cons比較
    メリット デメリット
    polling
    シンプルな実装 パフォーマンスが良くない
    silent push
    Pushの実装を使いまわせる 拡張性が低い
    Firebase
    SDKを使った実装がシンプル
    mBaasの基盤で安定
    Firebaseが使えない環境だと利用で
    きない
    WebSocket
    自由度 / 拡張性が高い 実装やメンテナンスコストが高い
    ● 要件 / 仕様と併せて最適なものを選ぶと良い

    View Slide

  38. 38
    clusterでの実例

    View Slide

  39. Cluster, Inc. All Rights Reserved. 39
    clusterでの実例
    ● メッセージや
    フレンドリクエストに利用

    View Slide

  40. Cluster, Inc. All Rights Reserved. 40
    clusterでの実例
    ● メッセージや
    フレンドリクエストに利用

    View Slide

  41. Cluster, Inc. All Rights Reserved. 41
    更新通知はWebSocketを採用
    ● 多くのPlatformをカバーできる
    ○ iOS / Android / Windows / Mac / Quest2
    ● inroom(Unity)内の通知にもWebSocketを利用
    ● FirebaseのDBを利用していない

    View Slide

  42. Cluster, Inc. All Rights Reserved. 42
    接続 / 切断の管理
    RealtimeSync
    Manager
    Auth API
    WebSocket
    token

    View Slide

  43. Cluster, Inc. All Rights Reserved. 43
    接続 / 切断の管理
    RealtimeSync
    Manager
    Auth API
    WebSocket
    token
    connect(token)

    View Slide

  44. Cluster, Inc. All Rights Reserved. 44
    接続 / 切断の管理
    RealtimeSync
    Manager
    Auth API
    WebSocket
    token
    connect(token)
    以下の状態をRealtimeSyncManagerが管理
    状態に変化がある度に接続(or 切断)処理を行う
    ・Login状態
    ・inroom / outroom
    ・Foreground / Background
    ・エラーなどでの切断

    View Slide

  45. Cluster, Inc. All Rights Reserved. 45
    更新通知の受信
    RealtimeSync
    Manager
    WebSocket
    Protocol Buffers
    Revisions

    View Slide

  46. Cluster, Inc. All Rights Reserved. 46
    更新通知の受信
    RealtimeSync
    Manager
    WebSocket
    Protocol Buffers
    Revisions
    初回接続時 or データ更
    新時に送られてくる
    struct Revisions {
    var messageRevision: UInt64
    var friendRevision: UInt64
    var fooRevision: UInt64
    var barRevision: UInt64
    }

    View Slide

  47. Cluster, Inc. All Rights Reserved. 47
    更新通知の受信
    RealtimeSync
    Manager
    WebSocket
    Protocol Buffers
    Revisions
    final class RealtimeSyncManager {
    var revisions: Observable {
    revisionsRelay.asObservable()
    }
    private var revisionsRelay = PublishRelay()
    // WebSocketからデータ受信毎に呼ぶ
    private func doReceive(_ data: Data) {
    // protobufをdecode
    guard let revisions = try? Revisions(contiguousBytes: data) else { return }
    revisionsRelay.accept(revisions)
    }
    }

    View Slide

  48. Cluster, Inc. All Rights Reserved. 48
    更新通知の受信
    RealtimeSync
    Manager
    WebSocket
    FriendSync
    subscribe
    Protocol Buffers
    Revisions
    final class FriendSync {
    private var currentRevision: UInt64
    private let bag = DisposeBag()
    init(realtimeSyncManager: RealtimeSyncManager, repository: FriendRepository) {
    realtimeSyncManager.revisions
    .subscribe(onNext: { [weak self] revisions in
    guard let self = self else { return }
    if self.currentRevision != revisions.friendRevision {
    self.currentRevision = revisions.friendRevision
    repository.fetchRequest()
    }
    })
    .disposed(by: bag)
    }
    }
    Revisionの
    更新チェック

    View Slide

  49. Cluster, Inc. All Rights Reserved. 49
    更新通知の受信
    RealtimeSync
    Manager
    WebSocket
    FriendSync
    subscribe
    Revisionの
    更新チェック
    Protocol Buffers
    Revisions
    FriendAPI
    Friend
    Repository
    fetch
    Friend
    ViewModel
    FriendView
    subscribe
    data bind
    更新をrequest

    View Slide

  50. Cluster, Inc. All Rights Reserved. 50
    更新通知の受信
    RealtimeSync
    Manager
    WebSocket
    FriendSync
    subscribe
    Revisionの
    更新チェック
    Protocol Buffers
    Revisions
    FriendAPI
    Friend
    Repository
    fetch
    Friend
    ViewModel
    FriendView
    subscribe
    data bind
    更新をrequest
    各画面共通の設計

    View Slide

  51. Cluster, Inc. All Rights Reserved. 51
    更新通知の受信
    RealtimeSync
    Manager
    WebSocket
    Sync
    subscribe
    Revisionの
    更新チェック
    Protocol Buffers
    Revisions
    API
    Repository
    fetch
    ViewModel View
    subscribe
    data bind
    更新をrequest
    各画面共通の設計
    新規でリアルタイム更新を追
    加する場合この部分を新設す
    るだけでOK

    View Slide

  52. 52
    UI更新時に考慮するポイント

    View Slide

  53. Cluster, Inc. All Rights Reserved. 53
    UIをリアルタイム更新するときに大事な事
    ● ユーザーの邪魔しない
    ○ ユーザーが何かを見ている途中でのインジケーター
    ○ エラー表示
    ○ スクロールしていた画面が勝手に戻る

    View Slide

  54. Cluster, Inc. All Rights Reserved. 54
    差分更新を使う
    ● DiffableDataSources
    var snapshot = NSDiffableDataSourceSnapshot()
    snapshot.appendSections([.section])
    snapshot.appendItems(items)
    dataSource.apply(snapshot)

    View Slide

  55. Cluster, Inc. All Rights Reserved. 55
    差分更新を使う
    https://developer.apple.com/videos/play/wwdc2020/10045/

    View Slide

  56. 56
    まとめ

    View Slide

  57. Cluster, Inc. All Rights Reserved. 57
    まとめ
    ● 情報をいち早く伝える必要がある場合にリアルタイム更新処理は必要
    ○ ex. チャットなど
    ● 更新通知の受信方法
    ○ polling, push通知, Firebase, WebSocket
    ● 対応画面を柔軟に増やせる更新通知の設計
    ● UI更新時にはユーザーの操作を邪魔しない

    View Slide

  58. 58
    最後に

    View Slide

  59. Cluster, Inc. All Rights Reserved. 59
    絶賛採用強化中!
    ● iOS Engineerはじめ各職種採用強化中です!
    https://recruit.cluster.mu/engineer/

    View Slide