Как разработчики, когда мы выпускаем новую версию нашего приложения с новыми функциями и исправлениями ошибок, мы хотим, чтобы наши пользователи как можно скорее обновились до последней версии.
Однако у многих пользователей на устройствах не включены автоматические обновления, и если они не открывают сам App Store и не ищут доступные обновления, они могут так и не узнать, что вышла новая версия вашего приложения.
Именно поэтому стоит сделать так, чтобы пользователи могли легко узнать о выходе новой версии вашего приложения прямо из самого приложения. Мы только что реализовали новую функцию в Helm, которая делает именно это, поскольку мы заметили, что многие пользователи сообщали об ошибках в старых версиях приложения:
В этой статье я покажу вам различные подходы к проверке последней доступной версии вашего приложения в App Store, которые вы можете использовать для очень простой реализации функции показа или принудительного обновления в вашем приложении.
Используя iTunes Lookup
У Apple есть специальная конечная точка, которую можно использовать для проверки информации о приложении в App Store по идентификатору: https://itunes.apple.com/lookup?id={id}
или по идентификатору бандла: https://itunes.apple.com/lookup?bundleId={bundleId}
.
Конечная точка возвращает JSON-объект со списком результатов, соответствующих указанному идентификатору, и каждый из результатов содержит информацию о приложении, включая номер версии и минимальную версию ОС, необходимую для запуска приложения:
import Foundation struct LookUpResponse: Decodable { let results: [LookUpResult] struct LookUpResult: Decodable { let version: String let minimumOsVersion: String let trackViewUrl: URL } } struct LatestAppStoreVersion { let version: String let minimumOsVersion: String let upgradeURL: URL } final class LookUpAPI { private let session: URLSession private let jsonDecoder: JSONDecoder init(session: URLSession = .shared, jsonDecoder: JSONDecoder = .init()) { self.session = session self.jsonDecoder = jsonDecoder } func getLatestAvailableVersion(for appID: String) async throws -> LatestAppStoreVersion? { let url = URL(string: "https://itunes.apple.com/lookup?appId=\(appID)")! let request = URLRequest(url: url) let (data, _) = try await session.data(for: request) let response = try jsonDecoder.decode(LookUpResponse.self, from: data) print(response) return response.results.first.map { .init(version: $0.version, minimumOsVersion: $0.minimumOsVersion, upgradeURL: $0.trackViewUrl) } } }
Хотя этот подход прост в реализации, поскольку запрос не требует аутентификации, у него есть довольно серьезное ограничение: он возвращает только один результат для каждого идентификатора приложения, что означает, что если для вашего приложения доступно несколько платформ (iOS, macOS, watchOS и т.д.), вы получите информацию только для одной из них.
Этот подход хорошо подошел нам, поскольку Helm доступен только на macOS, но он не сработает, если у вас есть приложение, которое доступно на нескольких платформах.
Используя App Store Connect API
Более надежный подход, хотя и более сложный, заключается в использовании App Store Connect API для получения всех доступных версий, готовых к распространению для каждой платформы вашего приложения.
К сожалению, App Store Connect API требует аутентификации всех запросов с помощью JWT-токена, который вы должны сгенерировать, используя ключ API, что значительно увеличивает сложность.
Процесс генерации JWT не так прост, поэтому я бы посоветовал вам использовать библиотеку вроде appstoreconnect-swift-sdk, которая сделает это за вас.
После того как аутентификация будет выполнена, вам останется только сделать запрос к конечной точке https://api.appstoreconnect.apple.com/v1/apps/{id}/appStoreVersions
с нужными параметрами и фильтрами и сопоставить полученный ответ с вашей моделью:
import Foundation import AppStoreConnect_Swift_SDK struct LatestAppStoreVersion { let version: String let minimumOsVersion: String let upgradeURL: URL } final class LatestVersionAPI { let provider: APIProvider init?() { guard let configuration = try? APIConfiguration( issuerID: "🙈", privateKeyID: "🙈", privateKey: "🙈") else { return nil } self.provider = APIProvider(configuration: configuration) } func getLatestAvailableVersion(for appID: String, platform: Platform) async throws -> LatestAppStoreVersion? { let filterPlatform: APIEndpoint.V1.Apps.WithID.AppStoreVersions.GetParameters.FilterPlatform = { switch platform { case .ios: return .ios case .macOs: return .macOs case .tvOs: return .tvOs case .visionOs: return .visionOs } }() let versionsRequest = APIEndpoint .v1 .apps .id(appID) .appStoreVersions .get(parameters: .init(filterAppVersionState: [.readyForDistribution], filterPlatform: [filterPlatform], fieldsAppStoreVersions: [.versionString, .platform], fieldsBuilds: [.minOsVersion], limit: 1, include: [.build])) let versionsResponse = try await provider.request(versionsRequest) let minimumOsVersion: String? = versionsResponse .included? .compactMap { item in if case let .build(build) = item { return build.attributes?.minOsVersion } return nil } .first guard let versionString = versionsResponse.data.first?.attributes?.versionString, let minimumOsVersion else { return nil } return LatestAppStoreVersion(version: versionString, minimumOsVersion: minimumOsVersion, upgradeURL: URL(string: "https://itunes.apple.com/app/id\(appID)")!) } } // Usage let api = LatestVersionAPI() let available = try await api?.getLatestAvailableVersion( for: "1596487035", platform: .visionOs )
Сравнение локальной и удаленной версий
Получив последнюю доступную версию приложения в App Store с помощью одного из двух подходов, вы можете сравнить ее с локальной версией вашего приложения и с версией системы, чтобы определить, нужно ли пользователю предлагать обновить приложение:
extension LatestAppStoreVersion { var shouldUpdate: Bool { guard let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { return false } let systemVersion = ProcessInfo().operatingSystemVersion let versionString = "\(systemVersion.majorVersion).\(systemVersion.minorVersion).\(systemVersion.patchVersion)" let isRemoteVersionHigherThanLocal = currentVersion.compare(self.version, options: .numeric) == .orderedAscending let isSystemVersionAllowed = versionString.compare(self.minimumOsVersion, options: .numeric) == .orderedDescending return isRemoteVersionHigherThanLocal && isSystemVersionAllowed } }