4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

記事投稿キャンペーン 「2024年!初アウトプットをしよう」

リアルタイム Remote Configで強制アップデートを実装する最小構成【Swift】

Posted at

はじめに

個人開発しているiOSアプリ「いいこと日記」に強制アップデートを実装する機会があり、手軽に導入できるFirebase Remote Configを使用しました。
Firebase SDKv10.7.0からサポートされているRemote Configのリアルタイム更新を使うことで、起動時だけでなくアプリの利用中でも即座に強制アップデートダイアログを表示することができます。
今回はできるだけ最低限の機能での実装を紹介します。

Firebaseでの設定

スクリーンショット 2024-01-17 6.35.07.png
Remote Config上にrequired_versionというパラメータを作成しておき、そのバージョンよりも古いバージョンだった場合に強制アップデートのダイアログを表示します。

実装

ForceUpdateManager.swift
import FirebaseRemoteConfig
import UIKit

final class ForceUpdateManager {
    static let shared: ForceUpdateManager = .init()

    private let remoteConfig = RemoteConfig.remoteConfig()
    private let requiredVersionKey = "required_version"

    private init() {}

    func startObserving() {
        // 初期データを取得する
        remoteConfig.fetchAndActivate { [weak self] status, error in
            switch status {
            case .successUsingPreFetchedData, .successFetchedFromRemote:
                self?.forceUpdateIfNeeded()
            default:
                if let error {
                    print(error.localizedDescription)
                }
            }
        }

        // Updateを監視する
        remoteConfig.addOnConfigUpdateListener { [weak self] configUpdate, error in
            guard let self else { return }

            if let error {
                print(error.localizedDescription)
            }

            // Fetchしてきたデータを有効化する
            remoteConfig.activate()

            // 強制アップデートを行う
            if configUpdate?.updatedKeys.first == requiredVersionKey {
                forceUpdateIfNeeded()
            }
        }
    }

    // バージョンを比較して強制アップデートが必要ならアラートを表示する
    private func forceUpdateIfNeeded() {
        guard let currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String,
              let requiredVersion = remoteConfig.configValue(forKey: requiredVersionKey).stringValue else {
            return
        }
        // バージョン文字列を配列に変換(例: "3.2.1" -> [3, 2, 1])
        var currentVersionNums = currentVersion.split(separator: ".").compactMap { Int($0) }
        var requiredVersionNums = requiredVersion.split(separator: ".").compactMap { Int($0) }
        if currentVersionNums.isEmpty || requiredVersionNums.isEmpty
            || currentVersionNums.count > 3 || requiredVersionNums.count > 3 {
            return
        }
        // 3桁まで0で埋める(例: [3, 2] -> [3, 2, 0])
        currentVersionNums.append(contentsOf: repeatElement(0, count: 3 - currentVersionNums.count))
        requiredVersionNums.append(contentsOf: repeatElement(0, count: 3 - requiredVersionNums.count))
        // バージョンを比較する
        for i in 0..<3 {
            let current = currentVersionNums[i]
            let required = requiredVersionNums[i]

            if current == required {
                continue
            } else if current > required {
                return
            } else if required > current {
                showAlert()
                return
            }
        }
    }

    private func showAlert() {
        // アラートを表示するViewController
        guard let viewController = rootViewController else { return }

        let alert = UIAlertController(title: "アップデートのお願い", message: "最新版にアップデートしてください。", preferredStyle: .alert)
        let updateAction = UIAlertAction(title: "App Storeを開く", style: .default) { _ in
            guard let url = URL(string: "https://apps.apple.com/app/{アプリのID}"),
                  UIApplication.shared.canOpenURL(url) else {
                return
            }
            UIApplication.shared.open(url)
            // ストア遷移時にアラートが閉じてしまうため再度表示する
            viewController.present(alert, animated: true)
        }
        alert.addAction(updateAction)

        viewController.present(alert, animated: true)
    }

    // 最前面のViewController
    private var rootViewController: UIViewController? {
        UIApplication.shared.connectedScenes
            .compactMap { $0 as? UIWindowScene }
            .first?
            .windows
            .filter { $0.isKeyWindow }
            .first?
            .rootViewController
    }
}

後はAppDelegateなどでForceUpdateManager.shared.startObserving()を一度呼ぶことで、Remote Configの変更をリアルタイムに取得することができます。

参考

https://firebase.google.com/docs/remote-config/get-started?platform=ios&hl=ja
https://qiita.com/Etsuwo/items/14c4698f17924923fc29
https://www.fuwamaki.com/article/258

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?