Swifter {Swift Developer}

메뉴

9. 오류 처리

[vc_row][vc_column][vc_column_text]Swift언어의 오류처리는 버전 1.0까지는 NSErrorPointer를 사용하여  NSError를 리턴하거나 그냥 리턴값에서 오류를 리턴하거나 직접 Either처리를 하고 오류처리와 정상적인 작업을 분리하는 방법이 사용되고 있었습니다. Swift 2.x의 오류처리에서는 예외 처리와 비슷한 개념이 도입되어 오류가 발생할 가능성이 있는 경우 메소드 선언을 하고 호출하는 측은 오류가 발생한다는 기준으로 소스코드를 만들어야 합니다. 이것이 보다 강력한 시스템을 만드는 방법중 하나라고 봅니다. 다만, 오류 핸드링은 assert와 fatalError를 체크하는 것은 아니기 때문에 주의해야 합니다.

이번 장에서는 오류를 발생하는 메소드와 처리하는 메소드, 빠져나올 때 처리방법을 알아봅시다.[/vc_column_text][vc_separator][vc_custom_heading text=”(1) 오류 발생
” use_theme_fonts=”yes”][vc_column_text]오류를 보내는 메소드(함수)는 오류를 보내고 명시적으로 선언해야 합니다. 이렇게 선언된 메소드나 함수를 사용한다면, 호출하는 측에서 오류를 처리하는 소스코드가 필요합니다. 단, 메소드 처리가 실패할 경우, 호출하는 측에 그 내용에 대해 알리거나 단순하게 nil을 리턴하는 경우가 있습니다. 앞의 방법은 오류 핸들링을 이용해야 하지만 뒷 방법은 복잡하게 사용할 필요가 없습니다.  사실 모든 오류 핸들링으로 처리하는 것이 효율적이지만 비동기 처리에서는 오류처리를 구현하지 못하는 경우도 있기 때문에 메소드를 설계할 단계부터 오류 핸들링을 이용할지를 고민해야 합니다.

호출자에게 오류를 보내려는 오류를 ErrorType의 enum으로 만들어야 합니다. 다만, NSError를 준비할 필요가 없는데 이유는 NSError자체가 ErrorType이 그대로 계승했기 때문입니다. 또한 오류가 발생하면 우선 메소드나 함수 정의에서 throws를 명시하고 실제 오류가 발생한 곳에서 throw를 사용합니다.[/vc_column_text][vc_column_text]

[code lang=”swift”]
func exam(msg: String) throws {
if msg.isEmpty {
throw NSError(domain: "오류", code: -1, userInfo: nil)
}
print(msg)
}

[/code]

[예제 2-85] NSError 사용
[/vc_column_text][vc_column_text]위 예제에서 처음 오류 처리가 필요한 메소드임을 나타내는 throws를 선언합니다. 그 다음 내부 로직에서 NSError를 throw해서 오류를 리턴하도록 합니다. throw 다음은 실행할 부분이 없기 때문에 return을 명시적으로 선언하지 않습니다.

그리고 다른 방법으로 로직처리중에 오류를 구분해서 보낼 경우에 사용하는 ErrorType기반으로 enum을 만들어 봅시다.[/vc_column_text][vc_column_text]

[code lang=”swift”]
enum ChangeError: ErrorType {
case Empty
case NotCode(code: Int)
}

func change(msg: String) throws -> Int {
if msg.isEmpty {
throw ChangeError.Empty
}
guard let num = Int(msg) else {
throw ChangeError.NotCode(code: 1)
}
return num
}
[/code]

[예제 2-86] ErrorType 사용[/vc_column_text][vc_column_text]enum으로 선언한 ChangeError는 2가지 오류를 정의한 것이지만 실행되고 있는 소스코드에서 ChangeError를 사용하고 있습니다.  파라미터에 값을 전달할 수 있다는 것도 기억하기 바라며 메소드의 인수에 오류가 발생할 수 있는 클로저를 사용할 경우 그대로 오류를 리턴해주고 싶은 경우가 있습니다. 이런 경우에는 throws 대신 rethrows를 정의해서 사용할 수 있지만 주의해야할 점은 rethrows를 지정하면 해당 메소드 내에서 새로운 오류를 보낼 수 없게 되므로 주의해야 합니다.[/vc_column_text][vc_column_text]

[code lang=”swift”]
func rethrowsExam(closure:(msg: String) throws -> String) rethrows ->String {
try closure(msg: "") // 오류 발생
return "오류없음"
}

func exam(msg: String) throws ->String {
if msg.isEmpty {
throw NSError(domain: "오류", code: 1, userInfo: nil)
}

return msg
}
[/code]

[예제 2-87] rethrows 사용[/vc_column_text][vc_column_text]rethrowsExam은 rethrows를 선언하고 오류가 발생할 수 있는 폐쇄를 받는 구조입니다. Try를 통해 오류가 발생할 수 있는 폐쇄를 호출하지만 오류처리는 구현되어 있지 않다는 것을 알수 있습니다.  이 부분에서 오류가 발생하면 rethrowsExam이 호출되어 오류를 그대로 보냅니다.[/vc_column_text][vc_custom_heading text=”(2) 오류 얻기
” use_theme_fonts=”yes”][vc_column_text]throws가 정의되어 있는 메소드를 호출시 오류를 받는 처리로직이 필요합니다. 오류를 받는 방법은 3가지가 용도에 따라 사용할 수 있습니다.

“do-try-catch”가 가장 범용성이 높은 오류처리방법으로 do 블록내에서 throws 또는 rethrows가 정의된 메소드를 호출하고 호출하려는 메소드는 try가 선언되기 전에 만들어야 합니다. 또한 이에 대한 정의를 하지 않으면 컴파일러에서 오류가 발생하기 때문에 주의해야 하고 catch블록에서 유라가 발생할 경우 진행할 작업을 작성합니다.[/vc_column_text][vc_column_text]

[code lang=”swift”]
enum ChangeError: ErrorType {
case Empty
case NotCode(code: Int)
}

func change(msg: String) throws -> Int {
if msg.isEmpty {
throw ChangeError.Empty
}
guard let num = Int(msg) else {
throw ChangeError.NotCode(code: 1)
}
return num
}

do {
let num = try change("가")
} catch ChangeError.Empty {
print("비어있음")
} catch ChangeError.NotCode(let code) {
print("코드 아닌 경우 code=(code)")
}
[/code]

[예제 2-88] do-try-catch 사용[/vc_column_text][vc_column_text]위 예제에서 catch문으로 2가지 오류를 구분할 수 있다는 것을 알 수 있습니다. 만약  한번에 오류를 받아서 처리하고자 한다면 판단해주는 구문을 작성하지 않고 catch문만 작성합니다.

do {

try …..

} catch {

// 모든 오류 체크

}

오류가 발생하면 처리르 계속 할 수 없는 상태 또는 오류가 발생시 처리를 계속 나오지 않게 하는 등의 오류를 받는 것이 아니라, 충돌시키고자 한다면 “try!”를 사용합니다. Try!를 사용하게 되면 do-catch문도 필요가 없습니다.

try! change(“메”)

그리고 오류가 발생해도 처리에 대한 것을 상세히 처리할 필요가 없다면, “try?”를 사용합니다. Try?를 사용하면 메소드 리턴값은 Optional형이 오류가 발생하면 리턴값은 nil이 되고 Optional형이 되기 때문에 간단하게 확인할 수 있습니다.

guard let num = try? change(“0”) else {

print(“오류”)

}

지금까지 오류 처리에 대해서 제대로 처리할 경우 오류가 발생해도 충돌시키거나 Optional형으로 리턴값을 리턴하는 방법등 용도에 따라 구분해서 사용합니다. 그리고 현재 오류 핸들링은 동기화만 취급하며 비동기로 오류를 리턴할 수 없다는 것을 기억하기 바랍니다.[/vc_column_text][vc_custom_heading text=”(3) 클린 처리” use_theme_fonts=”yes”][vc_column_text]메소드에서 나와 조건에 맞추는 경우와 오류에 의해 처리되는 도중에 나오는 경우가 있습니다. 오류 처리 중간에 종료하면 이전에 처리된 데이터(로직)이 남아 문제가 발생하는 경우가 있습니다. 성공한다고 해도 메소드가 실행을 끝낼 때 임시 파일 삭제와 같은 클린 처리가 필요합니다. 이런 뒷처리를 “defer”를 사용하여 블록단위로 만들 수 있습니다.

defer의 특징은 선언되고 있는 순서대로 처리안되고 뒤에서 위로 올라가면서 처리됩니다. 또한 오류에 의해 메소드에서 나오더라도 defer는 처리되지 않기 때문에 처리중에 뒷처리가 가능합니다.[/vc_column_text][vc_column_text]

[code lang=”swift”]
func exam() {
defer {
print("1번")
}

defer {
print("2번")
}

do {
try test(0)
} catch {
print("오류")
}

defer {
print("3번")
}
}
[/code]

[예제 2-89] defer 사용[/vc_column_text][vc_column_text]try test(0) 부분이 오류가 발생하지 않고 문제없이 메소드가 종료하면 결과는 다음과 같습니다.

“3번”

“2번”

“1번”

반대로 오류가 발생하면 catch문에 추가된 메소드로 실행됩니다.

“오류”

defer는 오류 이후 소스코드이기 때문에 실행되지 않습니다.[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_separator][vc_btn title=”목차로 가기” color=”orange” align=”center” link=”url:%2Fswift-2-korean-book%2F|title:%EB%AA%A9%EC%B0%A8|”][/vc_column][/vc_row]

Facebook Comments

카테고리:   Swift Books

댓글

죄송하지만 댓글은 닫혀 있습니다.