LoginSignup
0
1

More than 1 year has passed since last update.

iOSアプリバージョンを取得するユーティリティ

Last updated at Posted at 2022-06-26

バージョン取得ユーティリティの実装例

iOSアプリの現在のバージョンを取得するコードは以下のように実装できます。

Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String  // いわゆる「アプリバージョン」
Bundle.main.infoDictionary!["CFBundleVersion"] as! String  // いわゆる「ビルドバージョン」

これらをまとめたユーティリティは例えば以下のように実装できます。

public enum AppVersion {
    public static func appVersion() -> String {
        info(key: "CFBundleShortVersionString") as! String
    }

    public static func buildVersion() -> String {
        info(key: "CFBundleShortVersionString") as! String
    }

    private static func info(key: String) -> Any? {
        Bundle.main.infoDictionary![key]
    }
}
AppVersion.appVersion()  // ex: 3.4.0
AppVersion.buildVersion()  // ex: 2100

フレームワークでユニットテストを実行すると意図しない結果になる

最初はこれでよかったのですが、後からこのユーティリティをフレームワークに移動し、ユニットテストを書こうとしたら問題が出てきました。

ここで、フレームワークのアプリバージョンが3.4.0、ビルドバージョンが2100だとします。

※ユニットテストはQuick/Nimbleで書いてます。

class AppVersionSpec: QuickSpec {
    override func spec() {
        describe("appVersion()") {
            it("アプリバージョンを返すこと") {
                expect(AppVersion.appVersion()).to(equal("3.4.0"))
            }
        }

        describe("buildVersion()") {
            it("ビルドバージョンを返すこと") {
                expect(AppVersion.buildVersion()).to(equal(2100))
            }
        }
    }
}

上記のテストは失敗します。

3.4.0を期待しているところでは1.3.1(環境に依ります)、2100を期待しているところでは20076(環境に依ります)になるのです。

任意のバンドルを指定できるように改良する

どうやら、AppVersionの実装でメインバンドル(Bundle.main)を参照しているのが原因のようです。

ユニットテストの実装内でBundle.mainをprintしてみると、以下のパスが出力されました。

NSBundle </Applications/Xcode-13.3.1.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents> (loaded)

一方、Bundle(for: AppVersionSpec.self)をprintしてみると、以下のパスが出力されました。

NSBundle </Users/takehilo/Library/Developer/Xcode/DerivedData/Sample-gfdtgwqbgmitwoacobrheufigmgd/Build/Products/Debug-iphonesimulator/CommonExtensionTests.xctest> (loaded)

どうやら先ほどの1.3.1というのはXcodeのバージョンを指しているようです。フレームワークのユニットテストを実装したときのBundle.mainはフレームワークではなく特別に用意されたバンドルになるようですね。



そこで、バージョンの取得対象となるバンドルを指定できるように、AppVersionを改良しました。

public enum AppVersion {
    public static func appVersion(of bundleClass: AnyClass? = nil) -> String {
        info(of: bundleClass, key: "CFBundleShortVersionString") as! String
    }

    public static func buildVersion(of bundleClass: AnyClass? = nil) -> String {
        info(of: bundleClass, key: "CFBundleVersion") as! String
    }

    private static func info(of bundleClass: AnyClass? = nil, key: String) -> Any? {
        let bundle = bundleClass.map { Bundle(for: $0) } ?? Bundle.main
        return bundle.infoDictionary![key]
    }
}

プロダクションコードで使用する際は基本的にアプリ本体のバージョンを取得したいケースがほとんどなので、デフォルトでBundle.mainになるようにしています。

以下のように、引数に特に何も指定せずに実行するとアプリ本体のバージョンを取得できます。

AppVersion.appVersion()

一方、ユニットテストを実行する際は、以下のようにすることでユニットテストターゲットのバージョンを取得させることが可能です。

class AppVersionSpec: QuickSpec {
    override func spec() {
        describe("appVersion(of:)") {
            it("アプリバージョンを返すこと") {
                expect(AppVersion.appVersion(of: AppVersionSpec.self)).to(equal("1.0.0"))
            }
        }

        describe("buildVersion(of:)") {
            it("ビルドバージョンを返すこと") {
                expect(AppVersion.buildVersion(of: AppVersionSpec.self)).to(equal(1))
            }
        }
    }
}

AppVersionSpec.selfを指定することで、ユニットテストターゲットのバージョンを取得できます。

私の環境では、フレームワークのバージョンは更新していきますが、ユニットテストターゲットのバージョンは更新しません。なのであえてユニットテストターゲットのバージョンでテストすることで、バージョン更新のたびにユニットテストを修正するという手間がかからないようにしています。

0
1
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
0
1