[iOS] 앱 버전 체크 기능 만들기(강제종료)
개발자가 앱을 업데이트했는데, 기존과는 완전히 다른 데이터 구조로 변경하였거나, 사람들이 써야만 하는 기능을 추가한 경우에는 업데이트를 강제할 필요가 있다. 출근 전 상태를 점검하는 것처럼 앱도 앱스토어의 앱버전과 비교해서 중요 업데이트가 올라와 있으면 앱을 실행시키지 않고 앱을 강제로 업데이트하게 할 수 있는 방법을 구현해 보았다.
아래에서는 소프트웨어 버전 관리 "major.minor.patch" 중 minor 버전이 올라갔을 때, 앱 스토어 버전과 비교하여 업데이트 경고창을 띄우는 방법을 알아보겠다.
0. 사전 준비사항
: 앱 스토어에 등록이 되어 있어야 한다.
: 물론 앱은 존재해야 한다.
-> 앱 스토어의 앱 버전과 현재 빌드하려는 앱의 버전을 비교하는 것이기 때문에, 앱 스토어에 앱이 배포가 되어 있어야 한다.
1. AppVersion 클래스 생성
1) 기본 프로퍼티
struct AppVersion {
// 현재 버전 정보 : 타겟 -> 일반 -> Version
static let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
// 개발자가 내부적으로 확인하기 위한 용도 : 타겟 -> 일반 -> Build
static let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
static let appStoreOpenUrlString = "https://apps.apple.com/app/id\(Bundle.main.appID)"
...
}
appVersion은 현재 타겟으로 설정된 버전 정보를 불러오는 것이다.
buildNumber의 경우, 타겟설정에서 개발자가 설정한 빌드 넘버를 불러온다.
앱 스토어 오픈 url의 경우, 각자의 앱 id 번호를 넣어주면 유효한 url을 만들 수 있다.
2) 최신 앱 스토어 정보 확인
// 앱 스토어 최신 정보 확인
func latestVersion(completion: @escaping (String?) -> Void) {
let appleID = Bundle.main.appID
let urlString = "http://itunes.apple.com/lookup?id=\(appleID)&country=kr"
guard let url = URL(string: urlString) else {
print("url이 정확하지 않음")
completion(nil)
return
}
let task = URLSession.shared.dataTask(with: url) { (data, _, error) in
if let error = error {
print("URLSession 작업 오류: \(error)")
completion(nil)
return
}
guard let data = data else {
print("데이터 존재하지 않음.")
completion(nil)
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
if let results = json["results"] as? [[String: Any]],
let appStoreVersion = results.first?["version"] as? String {
completion(appStoreVersion)
} else {
print("JSON 파싱 실패 또는 예상하지 못한 형식: \(json)")
completion(nil)
}
}
} catch {
print("JSONSerialization 오류: \(error)")
completion(nil)
}
}
task.resume()
}
현재 앱 스토어에 있는 최신 버전을 불러오는 함수이다.
itunes와 관련된 url을 사용하는데, 여기에 http 방식으로 요청을 해서 데이터를 json 형태로 받아오면, 그중 version 데이터만 파싱 하여 리턴하는 방식으로 앱 버전을 가져온다. 이렇게 하면 이 함수를 호출했을 때 클로저 안에서 version을 받아 사용할 수 있다.
3) 현재 앱 스토어 버전과 빌드 버전을 비교하는 함수
// 버전 비교 (현재 버전과 앱스토어 버전 비교)
func isMinorVersionUpdated(currentVersion: String, appStoreVersion: String) -> Bool {
let currentVersionComponents = currentVersion.split(separator: ".").map { Int($0) ?? 0 }
let appStoreVersionComponents = appStoreVersion.split(separator: ".").map { Int($0) ?? 0 }
guard currentVersionComponents.count >= 2, appStoreVersionComponents.count >= 2 else {
return false
}
return appStoreVersionComponents[0] > currentVersionComponents[0] ||
(appStoreVersionComponents[0] == currentVersionComponents[0] &&
appStoreVersionComponents[1] > currentVersionComponents[1])
}
여기에서 업데이트를 할지 말지 정책을 결정할 수 있다.
이 코드의 경우, 버전의 맨 앞자리 major 또는 minor 버전이 앱 스토어의 것이 더 클 경우, 또는 major는 같은데 minor가 앱 스토어의 것이 더 클 경우, true를 반환하게 해서 관련 로직을 처리하게 해주는 것이다.
4) 앱 스토어 연결
// 앱 스토어로 이동 -> urlStr 에 appStoreOpenUrlString 넣으면 이동
func openAppStore() {
guard let url = URL(string: AppVersion.appStoreOpenUrlString) else { return }
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
앱 스토어로 연결시켜 주는 부분이다. 얼럿에서 확인 버튼을 누르면 열리도록 할 것이다.
2. 버전 확인 Extension
앱 엔트리포인트에 Extension을 활용하여 버전 체크 함수를 만든다.
extension MyApp {
func checkAppVersion() {
let version = AppVersion()
version.latestVersion { appStoreVersion in
guard let appStoreVersion = appStoreVersion else {
return
}
guard let currentVersion = AppVersion.appVersion else {
return
}
print("현재 앱버전: ", currentVersion)
print("앱스토어 앱버전: ", appStoreVersion)
if version.isMinorVersionUpdated(currentVersion: currentVersion, appStoreVersion: appStoreVersion) {
DispatchQueue.main.async {
showUpdateAlert = true
}
}
}
}
AppVersion에 있는 함수를 가지고 앱스토어 버전을 가져오고, 만약 업데이트가 필요하다면 showUpdateAlert 변수를 true 시켜서 얼럿 창이 나오도록 한다.
3. 강제종료의 방법
@main
struct MyApp: App {
@State private var showUpdateAlert = false
var body: some Scene {
WindowGroup {
VStack {
if !showUpdateAlert {
ContentView()
.onAppear {
checkAppVersion()
}
} else {
EmptyView()
.background(Color.backgroundColor)
}
}
.alert(isPresented: $showUpdateAlert) {
Alert(
title: Text("앱 버전 업데이트 필요"),
message: Text("앱 버전이 업데이트 되었습니다.\n앱 스토어에서 업데이트를 진행해주세요!"),
dismissButton: .default(Text("업데이트")) {
AppVersion().openAppStore()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
exit(0)
}
}
)
}
}
}
}
앱 엔트리 포인트에 이런 식으로 처리해 주었다.
처음에는 원래의 콘텐츠가 든 화면을 로딩하는데, checkAppVersion을 통해서 앱 버전을 체크하고, 만약, 앱 버전 업데이트가 필요하다면, 뒤의 화면 자체를 가려버리는 방법을 택했다.
Alert 자체가 나와도, 앱 스토어 화면으로 넘어갔다가 사용자가 다시 앱으로 넘어온다면 서비스를 사용할 수 있기 때문이다. 원천 차단하기 위해서 뷰 자체를 보여주지 않는 방법으로 구현을 했다. 다만, 이 방법이 업데이트가 되고 재구동을 했을 때 문제가 되므로, 앱 스토어로 넘어간 경우, 2초 정도 시간 후에 앱을 강제 종료 해주는 exit(0) 코드를 넣어주었다. 이렇게 하면 앱이 종료가 되어 버리기 때문에, 앱을 다시 켰을 때 showUpdateAlert 변수 문제로 인해 화면이 의도하지 않은 대로 나오는 문제를 해결할 수 있었다.
이것을 만약에 시험해보고 싶다면, 로컬에 있는 본인의 빌드 버전을 강제로 낮추면 된다. 낮춰서 빌드를 해보고 다시 원래의 버전으로 돌리는 것은 잊지 말아야 한다!
'iOS' 카테고리의 다른 글
[iOS] SwiftUI 약관 동의 화면, 전체 동의 Checkbox 만들기 (0) | 2024.08.09 |
---|---|
[iOS] SwiftUI Navigation BackButton 일괄 변경하기 (1) | 2024.08.08 |
[iOS] Firebase만 가지고 리더보드 만들어보기 (0) | 2024.07.29 |
[iOS] Naver Search API로 지역 검색 구현하기 (3) | 2024.07.22 |
[iOS] 커스텀 버튼 컴포넌트 하이라이트 처리 (0) | 2024.01.08 |
댓글