最新バージョンのアップデートがあることをユーザーに知らせ、アップデートに誘導したい。
毎回手動でアプリ内にハードコーディングしたり、Firebase Remote Configで制御するのは運用コストが上がりバグの原因にもなる。
この操作を完全に自動化する。
はじめに
具体的にはAppStoreのAPIを利用して最新バージョンを取得する。
https://itunes.apple.com/lookup?id={APP ID}&country=JP
このAPIを叩くとAppStoreに乗っている様々な情報を取得できる。例えば、最新バージョン、最小のOS、リリースノート、ファイルサイズなどAppStoreに表示されているほとんどの情報が取得できる。ただ、国ごとにLocalizeしている場合Queryでcountry=JP
などと指定する必要がある。
今回はこのうち必要な情報だけパースすることにする。
以下を取得する
public import Foundation
public struct AppStoreAppInfoResponse: Sendable, Codable {
public let results: [AppStoreAppInfo]
public init(results: [AppStoreAppInfo]) {
self.results = results
}
}
public struct AppStoreAppInfo: Sendable, Codable, Hashable {
public let version: String
public let minimumOsVersion: String
public let trackViewUrl: URL
public let releaseNotes: String
public let description: String
public let fileSizeBytes: String
public init(version: String, minimumOsVersion: String, trackViewUrl: URL, releaseNotes: String, description: String, fileSizeBytes: String) {
self.version = version
self.minimumOsVersion = minimumOsVersion
self.trackViewUrl = trackViewUrl
self.releaseNotes = releaseNotes
self.description = description
self.fileSizeBytes = fileSizeBytes
}
}
次に取得したバージョンと現在のバージョンを比較するためComparableに準拠したStructを定義する。
import Foundation
public struct AppSemanticVersion: Sendable, Codable, Hashable {
public struct VersionError: Error {}
let major: Int
let minor: Int
let patch: Int
public init(major: Int, minor: Int, patch: Int) {
self.major = major
self.minor = minor
self.patch = patch
}
public init(from versionString: String) throws {
let versionNumbers = versionString.split(separator: ".").compactMap { Int($0) }
guard versionNumbers.count == 3 else {
throw VersionError()
}
self.major = versionNumbers[0]
self.minor = versionNumbers[1]
self.patch = versionNumbers[2]
}
public func versionString() -> String {
"\(major).\(minor).\(patch)"
}
}
// MARK: Protocol Conformance
extension AppSemanticVersion: Comparable {
public static func < (lhs: AppSemanticVersion, rhs: AppSemanticVersion) -> Bool {
if lhs.major != rhs.major {
return lhs.major < rhs.major
} else if lhs.minor != rhs.minor {
return lhs.minor < rhs.minor
} else {
return lhs.patch < rhs.patch
}
}
}
extension AppSemanticVersion: Equatable {
public static func == (lhs: AppSemanticVersion, rhs: AppSemanticVersion) -> Bool {
return lhs.major == rhs.major &&
lhs.minor == rhs.minor &&
lhs.patch == rhs.patch
}
}
これで準備が整ったので実際に最新バージョンを取得するためのAPIを叩く
/// AppStoreから最新のバージョン情報を取得.
func getLatestVersionInfo() async -> AppStoreAppInfo? {
guard let url = URL(string: "https://itunes.apple.com/lookup?id={Apple ID}&country=JP") else { return nil }
let request = URLRequest(url: url)
guard let (data, _) = try? await URLSession.shared.data(for: request) else { return nil }
let response = try? JSONDecoder().decode(AppStoreAppInfoResponse.self, from: data)
return response?.results.first
}
また、現在のアプリのバージョンをBundle.main
から取得
let currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString")
取得したバージョンが最新か調べる
/// AppStoreから取得したAppStoreAppInfoから現在のバージョンが最新であるか確認する
/// フォーマットが不正なことはまず起こり得ないが、起こった場合はtrueを返す.
func isLatestVersion(currentVersion: String, appInfo: AppStoreAppInfo) -> Bool {
guard let currentAppVersion = try? AppSemanticVersion(from: currentVersion) else { return true }
guard let newAppVersion = try? AppSemanticVersion(from: appInfo.version) else { return true }
return currentAppVersion >= newAppVersion
}
以上で最新バージョンを自動で取得でき、最新バージョンがAppStoreに上がっていれば自動でユーザーに知らせることができる
guard let latestVersionInfo = await getLatestVersionInfo(),
let currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String else {
return
}
if !isLatestVersion(currentVersion: currentVersion, appInfo: latestVersionInfo) {
/// アラートなどでユーザーに知らせる
/// AppStoreAppInfoで最新バージョンのリリースノートもパースしているのでこれを直接表示するなど
}