본문 바로가기
iOS

[iOS] 앱 버전 체크 기능 만들기(강제종료)

by DuncanKim 2024. 8. 7.
728x90

[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 변수 문제로 인해 화면이 의도하지 않은 대로 나오는 문제를 해결할 수 있었다.

 

이것을 만약에 시험해보고 싶다면, 로컬에 있는 본인의 빌드 버전을 강제로 낮추면 된다. 낮춰서 빌드를 해보고 다시 원래의 버전으로 돌리는 것은 잊지 말아야 한다!

 

 

728x90

댓글