예거's Bicycle for the mind

[iOS] fatal error 발생했을 때, 우아하게 앱 종료시키는 방법 (How to terminate iOS Application gracefully) 본문

iOS & Swift

[iOS] fatal error 발생했을 때, 우아하게 앱 종료시키는 방법 (How to terminate iOS Application gracefully)

유예거 2021. 11. 28. 06:26

마지막 catch 의 역할

iOS 앱 개발을 하다보면, 발생 가능한 에러를 정의하는 열거형에러를 발생시킬 가능성이 있는 메서드를 만들게 됩니다.

 

에러를 던질 수 있는(throws) 메서드는 호출부에서 do-try-catch 문으로 처리해줄 수 있는데요, 이때 직접 정의한 에러 케이스를 전부 다루더라도, exhaustive 하지 않다는 컴파일 에러가 뜨는 경우가 있습니다.

 

 

exhaustive"하나도 빠뜨리는 것 없이 철저한" 이라는 뜻의 형용사인데요.

스위프트에서 switch 문을 쓰다 보면, 자주 보실 수 있는 에러죠.

 

 

위의 예시 코드처럼, do-try-catch 문에서 모든 에러 타입을 catch 로 붙잡아주더라도, 마지막에 매치되는 에러가 없는 catch 절이 필요한데요.

마지막 catch 절은 예상하지 못한 에러가 발생했을 때, 그걸 잡아주는 역할을 합니다. (Unexpected error)

 

지금까지 여러 프로젝트를 하면서, 이 마지막 catch 절은 "발생하지 않을 에러" 내지는 "발생 가능성이 극도로 낮은 에러" 라고 생각한 게 사실입니다.

 

그래서 간단하게 어떤 에러가 나왔는지 출력만 해주는 print 문을 넣고 return 시키거나

혹은 치명적인 에러가 나왔으니, 앱을 강제로 종료시키는 fatalError 코드를 넣은 적도 있는데요. 😅

 

do {
    try someMethodThatThrowsErrors()
    } catch someError.errorOne {
        doSomeAction()
    } catch someError.errorTwo {
        doSomeAction2()
    } catch {
    	// 간단하게 print 문 삽입 후 return 시키거나
        print("Unexpected error: \(error).")
        return
        
        // 발생하지 않을 에러라 생각해 fatalError 코드 삽입하거나
        fatalError("Unexpected Error")
    }

 

비록 발생 가능성이 매우 낮더라도, 예상치 못한 런타임 에러로 앱이 강제 종료되는 경우가 있다면, App Store 심사 지침에 따라 앱 스토어 등록이 거부될 수 있다고 합니다. 출처

 

We found that your app includes a UI control for quitting the app. This is not in compliance with the iOS Human Interface Guidelines, as required by the App Store Review Guidelines.

 

그렇다면, 개발자가 예상하기 어려운 치명적인 에러가 발생한 경우엔 어떻게 대응해야 할까요?

 

애플이 좋아하는 앱 종료 방법

오래된 문서이긴 하지만, 애플이 graceful termination, 즉 우아한 종료에 대해 언급한 문서를 살펴보겠습니다.

 

There is no API provided for gracefully terminating an iOS application.

In iOS, the user presses the Home button to close applications.
Should your application have conditions in which it cannot provide its intended function, the recommended approach is to display an alert for the user that indicates the nature of the problem and possible actions the user could take — turning on WiFi, enabling Location Services, etc.

Allow the user to terminate the application at their own discretion.

⚠️ Warning: Do not call the exit function.
Applications calling exit will appear to the user to have crashed, rather than performing a graceful termination and animating back to the Home screen.

 

요약하자면, iOS 앱에는 우아한 종료를 위한 API 가 없다고 합니다.

앱을 종료하려면 유저가 홈 버튼을 눌러야 한다고 써있네요.

 

즉, 우리에게 익숙한 '앱 스위처(App Switcher)'를 이용해서 앱을 종료하라는 말입니다.

* 참고로 App Switcher 라는 명칭은 iPhone User Guide 에 나오는 공식적인 명칭입니다!

 

 

그리고 앱이 제기능을 못하는 상황에서 권장되는 방법은, 유저에게 어떤 문제가 발생했는지와 선택 가능한 선택지를(possible actions) Alert 로써 보여주는 것입니다.

유저가 자신의 재량에 따라(own discretion) 앱을 종료할지 말지를 선택할 수 있게 만들자는 내용입니다.

 

그리고 그 아래 ⚠️ Warning 부분에서는 exit() 메서드를 호출하지 말라고 합니다.

exit() 메서드가 호출되면, 유저는 앱이 갑자기 크래시가 난 것처럼 느낄 수 있기 때문입니다.

그리고 뒤에 나오는 문장으로, 애플이 권장하는 앱 종료의 방법을 알 수 있습니다.

 

performing a graceful termination and animating back to the Home screen.

 

우아하게 종료하고, 애니메이팅을 통해 홈 스크린(홈 화면)으로 돌아가는 것이죠.

 

정리하자면, 앱이 제기능을 못하는 상황에서 애플이 권하는 앱 종료 방법

 

1. 유저에게 어떤 문제가 발생했는지와 선택 가능한 액션을 Alert 로 보여준다.

2. 갑자기 앱이 꺼지는 것처럼 느껴지지 않게, 홈 스크린으로 돌아가는 애니메이션과 함께 우아하게 앱을 종료시킨다.

 

그럼 한 번 만들어볼까요? 😊

 

우아하게 앱 종료시키기

// second 파라미터로 들어오는 숫자 초만큼 기다린 뒤, 홈 스크린으로 나가면서 앱 종료시키는 메서드
func terminateAppGracefullyAfter(second: Double) {
    DispatchQueue.main.asyncAfter(deadline: .now() + second) {
        UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            exit(1) // Exit Failure
        }
    }
}

// 유저에게 치명적 오류가 발생했음을 알리고, 앱 종료에 대한 선택권을 주는 Alert
func showAppTerminatingAlert() {
    let title = "시스템 오류가 발생했습니다."
    let message = "앱이 5초 뒤 종료됩니다...\n개발자에게 문의해주세요."
    let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
    let terminateAction = UIAlertAction(title: "지금 종료", style: .destructive) { _ in
        self.terminateAppGracefullyAfter(second: 0) // 즉시 우아한 앱 종료
    }
        
    alert.addAction(terminateAction)
    present(alert, animated: true) {
        self.terminateAppGracefullyAfter(second: 5.0) // 5초 후 우아한 앱 종료
    }
}

// do-try-catch 문
do {
    try someMethodThatThrowsErrors()
    } catch someError.errorOne {
        doSomeAction()
    } catch someError.errorTwo {
        doSomeAction2()
    } catch {
    	// 우아한 앱 종료 Alert 메서드 삽입
        showAppTerminatingAlert()
}

 

먼저, terminateAppGracefullyAfter(second: Double) 메서드는 second 파라미터로 입력받은 숫자 '초'만큼 기다린 뒤에, 앱을 suspend 상태로 보내버립니다.

이 부분에서 홈 스크린으로 나가는 듯한 애니메이션이 작동합니다.

 

그리고 바로 아래 .now() + 0.5 코드에서 0.5초를 기다리는 이유가 있는데요.

홈 스크린으로 나가는 애니메이션도 0.3 ~ 0.4초 정도의 시간이 걸리기 때문입니다.

 

애니메이션이 재생 중일 때 앱을 강제 종료시키면, 부드럽게 종료되다가 갑자기 끊기는 느낌이 나거든요. 😅

그래서 0.5초 정도 충분히 기다려준 뒤에 exit() 메서드로 앱을 강제 종료시킵니다.

 

* 참고로 exit(0) -> 성공적인 프로그램 종료 (Exit Success)

exit(1) -> 성공적이지 않은 프로그램 종료 (Exit Failure) 라고 보면 됩니다.

예시에서는 런타임 에러 상황을 가정했기 때문에, exit(1) 코드를 넣었습니다.

 

Alert 메서드에서는 "지금 종료" 라는 버튼을 하나 만들어서, 유저가 누르면 즉시 앱이 부드럽게 종료되도록 구현했습니다.

애플이 말한 대로, 유저에게 나름의 '선택권'을 준 셈이죠. (사실 안 눌러도 5초 뒤에 앱이 꺼지는 건 똑같습니다...)

 

마지막으로, 위 코드를 실제로 적용한 모습을 첨부하겠습니다.

이걸 보면 우아한 앱 종료가 무엇인지 확 와닿으실 거예요. 😄

 

 

참고 링크

[애플 공식 문서] Error Handling

[애플 공식 문서] How do I programmatically quit my iOS application?

[iPhone User Guide] Switch between open apps on iPhone

[iPhone User Guide] iPhone 또는 iPod touch에서 앱 닫기

[Zedd 블로그] 앱 종료하기 with animation

[백투더맥 블로그] iOS에서 앱 강제 종료하지 마세요

Comments