[Swift] Error Handling


Error Handling


에러 처리

  • 컴파일 타임 에러
    • 스위프트 문법과 관련된 에러
      (컴파일러가 미리 알고 수정해야한다고 알려줌)
  • 런타임 에러
    • 프로그램이 실행되는 동안 발생
    • 크래시(앱이 강제로 종료)
      • 발생 가능한 에러를 미리 처리해 두면, 강제 종료되지 않음
        (개발자가 처리해야 하는 에러)

에러 처리가 필요한 이유

  • 어떤 얘기치 못한 상황이 발생할 수 있음
    • 네트워크 통신을 하는 등의 경우에서 서버에서 데이터를 못 받아와서 앱이 꺼짐
  • 프로세스 중에서, 예외적인 상황(에러)이 발생하는 것이 미리 가능성 등을 처리해 놓으면 앱이 무작정 꺼지는 것을 예방 가능
    • 에러를 통해 앱이 그냥 꺼지는 것 보다 알림을 통해 문제가 발생함을 유저에게 알려주는 것이 나음
  • 에러는 일반적으로 함수의 처리과정에서 일어남
    • 함수를 정의할때, 예외적인 일이 발생하는 경우가 발생할 수 있는 함수라고 정의하고 처리하는 과정이 에러 핸들링

에러 처리를 한 함수의 형태

  • () throws -> ()
  • (Int) throws -> ()

에러 처리 과정

// 1) 에러 정의 (어떤 에러가 발생할지 경우를 미리 정의)
enum HeightError: Error {//에러 프로토콜 채택 (약속)
    case maxHeight
    case minHeight
}

// 2) 에러가 발생할 수 있는 함수에 대한 정의
func checkingHeight(height: Int) throws -> Bool {
// 에러를 던잘수 있는 함수 타입
    if height > 190 {
        throw HeightError.maxHeight
    } else if height < 130 {
        throw HeightError.minHeight
    } else {
        if height >= 160 {
            return true
        } else {
            return false
        }
    }
}

// 3) 에러가 발생할 수 있는 함수의 처리(함수의 실행)
// try와 do-catch문으로 처리
do {
    // 함수 앞에 try를 붙여줘야함
    let isChecked = try checkingHeight(height: 200)
    print("놀이기구 타는 것 가능: \(isChecked)")
} catch {
    print("놀이기구 타는 것 불가능")
}

// do 블럭 : 함수를 통한 정상적인 처리의 경우 실행하는 블럭
// catch 블럭 : 함수가 에러를 던졌을 경우의 처리 실행하는 블럭

에러 처리 방법

try

do {
    let data = try parsing()
} catch {
    // ...
}
  • 에러 정식 처리 방법
  • 모든 에러 발생의 예외적인 경우를 디테일하게 처리 가능

try? (Optional try)

let data = try? parsing()
  • 결국 옵셔널 타입으로 리턴하기 때문에 언래핑 후 사용해야 함
    • 정상적인 경우 : 정상 리턴 타입으로 리턴
    • 에러가 발생 : nil 리턴

try! (Forced try)

let data try! parsing()
  • 에러가 발생할 가능성이 없는 경우에 제한적으로 사용
    • 정상적인 경우 : 정상 리턴 타입으로 리턴
    • 에러가 발생 : 런타임 에러
  • 에러가 발생할 수 없다고 확신이 있는 경우만 사용해야 함

catch 블럭 처리법

  • catch 블럭은 do 블럭에서 발생한 에러만을 처리하는 블럭
  • 모든 에러를 반드시 처리해야만 함
    (글로벌 스코프에서는 모든 에러를 처리하지 않아도 컴파일 에러가 발생하지 않음)

1) 패턴을 모두 정의 (모든 에러 경우를 각각 따로 처리)

  • 각 catch 블럭에서 모든 경우를 정의하거나, 구체적인 경우를 먼저 정의하고, 마지막엔 catch 키워드만 있어도 됨
    (Swift 5.3 버전에서는 catch 뒤에 케이스 여러개 나열 가능)
// 상단 에러 처리 과정 3번 do-catch문 참고
do {
    let isChecked = try checkingHeight(height: 100)
    print("놀이기구 타는 것 가능: \(isChecked)")
    
} catch HeightError.maxHeight  {  // where절을 추가해서, 매칭시킬 에러패턴에 조건을 추가할 수 있음
    print("키가 커서 놀이기구 타는 것 불가능")
} catch HeightError.minHeight {  // 생략가능
    print("키가 작아서 놀이기구 타는 것 불가능")
}

2) catch 패턴 없이 에러를 받아서 분기 처리로 처리

  • catch 블럭 내에서 error 상수 제공
  • error 상수(에러 프로토콜 타입)를 구체적으로 정의한 타입으로 캐스팅해서 스위치문 등으로 다시 처리
// 상단 에러 처리 과정 3번 do-catch문 참고
do {
    let isChecked = try checkingHeight(height: 100)
    print("놀이기구 타는 것 가능: \(isChecked)")
} catch {  // error 프로토콜에서 error 상수를 제공 (모든 에러가 넘어옴)
    print(error.localizedDescription)

    // 실제 우리가 정의한 구체적인 에러 타입이 아니고, 에러 타입(프로토콜)이 넘어올 뿐
    if let error = error as? HeightError {
        switch error {
        case .maxHeight:
            print("키가 커서 놀이기구 타는 것 불가능")
        case .minHeight:
            print("키가 작아서 놀이기구 타는 것 불가능")
        }
    }
}

에러를 던지는 함수를 처리하는 함수

// 에러정의
enum SomeError: Error {
    case aError
}

// 에러를 던지는 함수 정의 (무조건 에러를 던진다고 가정)
func throwingFunc() throws {
    throw SomeError.aError
}

// 에러의 처리
do {
    try throwingFunc()
} catch {
    print(error)
}
  1. 함수 내부에서 에러 함수를 다루는 경우
  • throwing 함수로 에러 다시 던지기
  • 함수 내에서 에러를 직접 처리하지 못하는 경우, 에러를 다시 던질 수 있음
// 함수 내부에서 do-catch문으로 에러를 처리
// 발생한 에러를 catch블럭에서 받아서 알맞은 처리
func handleError() {
    do {
        try throwingFunc()
    } catch {
        print(error)
    }
}

// do-catch블럭이 없어도 에러를 밖으로 던질 수 있음
func handleError1() throws {
    //do {
    try throwingFunc()
    //}
}

do {
    try handleError1()  // 에러를 받아서 처리 가능
} catch {
    print(error)
}
  1. 콜백 함수로 에러를 던지는 함수가 쓰이는 경우
  • rethrowing 함수로 에러 다시 던지기
  • 에러를 던지는 throwing 함수를 파라미터로 받는 경우, 내부에서 다시 에러를 던지기 가능
  • rethrows 키워드 사용 (rethrowing 메서드)
// 다시 에러를 던지는 함수(방법1)
func someFunction1(callback: () throws -> Void) rethrows {
    try callback()  // 에러를 다시 던짐(직접 throw 키워드로 던지지는 못함)
}

// 다시 에러를 던지는 함수(방법2) - 에러변환
func someFunction2(callback: () throws -> Void) rethrows {
    enum ChangedError: Error {
        case cError
    }
    
    do {
        try callback()
    } catch {  // 파라미터로 사용된 함수만 catch절에서 제어 가능
        throw ChangedError.cError  // catch 구문에서는 throw 키워드로 다시 던지기 가능
    }
}

// 실제 에러를 다시던지는(rethrowing)함수를 처리하는 부분
do {
    try someFunction1(callback: throwingFunc)
} catch {
    print(error)
}

do {
    try someFunction2(callback: throwingFunc)
} catch {
    print(error)
}

메서드/생성자에 throw 키워드 적용

  • 에러는 throwing 함수 뿐만 아니라, 메서드/생성자에도 적용 가능
  • 에러를 던질 수 있는 메서드는 throwing 메서드, 에러를 던질 수 잇는 생성자는 throwing 생성자
  • 생성자/메서드 재정의
    • throwing 메서드/생성자는 재정의 할 때, 반드시 throwing 메서드/생성자로 재정의 해야함
      (throwing 메서드/생성자를 일반 메서드/생성자로 재정의 불가)
    • 상속 관계에서 범위 관계
      • 일반 함수 > throwing 함수 > rethrowing 함수
      • 상속 관계를 고려한 큰 범위에서 작은 범위로의 재정의는 가능

참고

Ellen Swift Class






© 2021. by hminkim