에러 처리(Error Handling)

썹스·2022년 11월 20일
0

Swift 문법

목록 보기
43/68

에러(Error)

Swift에서 에러의 종류와 원인은 정말 다양하지만, 크게 컴파일 에러(Compile Error)런타임 에러(Runtime Error)로 나뉩니다.

📌 컴파일 에러(Compile Error)

  • 컴파일 에러는 개발자가 코드를 잘못 작성하여 발생하는 에러입니다.

  • 컴파일 에러가 발생 시 컴파일러가 에러 메시지를 개발자에게 보여주기 때문에 비교적 쉽게 문제점을 해결할 수 있습니다.

📌 런타임 에러(Runtime Error)

  • 코드의 문법적인 문제 없지만, 코드 실행 시 여러 가지 요인(설계 미숙, 언어 및 IDE의 업데이트) 때문에 발생하는 에러입니다.

  • 런타임 에러는 즉각적으로 확인할 수 없기 때문에 에러 처리 문법을 사용하여 에러 발생을 대처해야 합니다.

에러 처리(Error Handling)

코드 실행 중 런타임 에러가 발생하면 프로그램이 강제로 다운되는 "크래시(crash)" 현상이 발생할 수 있기 때문에 적절한 조치가 필요합니다.

📌 에러 처리의 3단계 과정

Swift의 에러 처리는 크게 3단계로 나뉩니다.
1️⃣ 에러 정의 (어떤 에러가 발생할지 미리 정의)
2️⃣ 에러가 발생할 수 있는 함수 정의
3️⃣ 에러가 발생할 수 있는 함수 실행

✅ 1단계: 에러 정의

어떤 경우의 에러가 발생할지 미리 정의해야 합니다.

개발자는 열거형 타입으로 에러를 정의해야 하며, 에러 프로토콜(Error)을 채택해야 합니다.

enum NameError: Error{  // 개발자가 만든 열거형 타입의 에러에 에러 프로토콜(Error)을 채택
    case noName
}

✅ 2단계: 에러 발생 함수 정의

에러가 발생할 수 있는 함수를 정의할 때는 파라미터(parameter) 괄호 다음에 throws라는 키워드를 작성해야 합니다.

throws라는 키워드를 통해 해당 함수는 "에러를 던질 수 있는 함수 타입"으로 변합니다.

func checkingName(name: String) throws -> String{   // throws라는 키워드를 작성
    if name.isEmpty{
        throw NameError.noName   // 에러를 던지는 코드
    }
    else{
        return name
    }
}

✅ 3단계: 에러 발생 함수 실행

throws라는 키워드가 들어간 함수는 바로 사용할 수 없습니다.

에러 발생 함수를 실행하기 위해서는 "do{ try }, catch{ }" 문법을 사용해야 합니다.

do{     // 함수를 실행하는 블럭
    try print(checkingName(name: "김철수"))
}
catch{  // 에러를 처리(실행)하는 블록
    print("이름이 없습니다.")
}

✅ 종합적인 코드

// 1단계 에러 정의
enum NameError: Error{  // 개발자가 만든 열거형 타입의 에러에 에러 프로토콜(Error)을 채택
    case noName
}


// 2단계 에러 발생 함수 정의
func checkingName(name: String) throws -> String{
    if name.isEmpty{
        throw NameError.noName   // 에러를 던지는 코드
    }
    else{
        return name
    }
}


// 3단계 에러 발생 함수 실행
do{     // 정상적인 처리(실행)를 하는 블럭
    try print(checkingName(name: ""))
}
catch{  // 에러를 처리(실행)하는 블록
    print("이름이 없습니다.")
}


/*
출력 결과
이름이 없습니다.
*/

📌 에러 처리의 3가지 방법(try, try?, try!)

✅ try

  • 일반적인 방법의 에러 처리입니다.
  • 일반적인 에러를 처리할 때는 do{ 정상 실행 } 문과 catch{ 에러 실행 } 문을 각각 작성해야 합니다.
do{
    try print(checkingName(name: ""))
}
catch{
    print("이름이 없습니다.")
}

✅ try? (옵셔널 트라이)

  • 함수의 결과를 옵셔널 타입으로 리턴하는 방식의 에러 처리입니다.
  • 정상 실행의 경우에는 옵셔널 타입으로 값을 리턴합니다.
  • 에러 실행의 경우에는 nil을 리턴합니다.
  • 에러 실행 시 nil을 리턴하기 때문에 catch{ 에러 실행 }문을 작성할 필요가 없습니다.
// 에러 발생시 nil 리턴
do{
    try? print(checkingName(name: ""))   // 이름이 없기 때문에 에러 발생
}

/*
출력 결과
<출력 결과가 없습니다.>
*/




// 정상 실행 결과는 옵셔널 타입으로 리턴됩니다.
do{
    var kim = try? checkingName(name: "김철수")
    print(kim)   // Optional("김철수")
}

/*
출력 결과
 Optional("김철수")
*/

✅ try! (Forced 트라이)

  • 정상 실행의 경우에는 정상 타입으로 값을 리턴합니다.
  • 에러 실행의 경우에는 런타임 에러가 발생합니다.
  • 에러 실행의 경우에는 런타임 에러가 발생하기 때문에 catch{ 에러 실행 } 문을 작성할 필요가 없습니다.
  • 에러가 발생할 수 없다고 확신하는 경우에만 사용합니다.
do{
    var kim = try! checkingName(name: "김철수")
    print(kim)   // 김철수
}

/*
출력 결과
김철수
*/


do{
    var kim = try! checkingName(name: "")     // 🚨에러 발생
    print(kim)   // 김철수
}

📌 catch{ } 블럭 처리법

catch{ } 블럭의 개수 또는 형태에 따라 다양한 경우의 에러를 자세하게 처리할 수 있습니다.

(에러를 자세하게 처리하고 싶을 때는 catch{ } 블럭을 여러 개 작성하면 됩니다.)

✅ 기본적인 Catch블럭 처리법

enum ageError: Error{
    case maxAge
    case minAge
}


func checkingAge(age: Int) throws -> String{
    if age < 1{
        throw ageError.minAge
    }
    else if age > 19{
        throw ageError.maxAge
    }
    else{
        return "미성년자 입니다."
    }
}


do{
    try print(checkingAge(age: 0))
}
catch{  // catch 블록이 1개인 경우 자세한 에러 처리가 힘들다. (1개의 catch 블록이 디폴트 에러처리 블록입니다.)
    print("나이 범위를 벗어났습니다.")
}


/*
 처리 결과
 나이 범위를 벗어났습니다.
 */

✅ 모든 에러 패턴을 정의

  • 각 catch블럭에 모든 경우의 에러를 정의
do{
    try print(checkingAge(age: 0))
}
catch ageError.maxAge{
    print("성인 입니다.")
}
catch ageError.minAge{
    print("나이가 0살 이하입니다.")
}
catch{  // 디폴트 에러
    print("에러 발생")
}


/*
 처리 결과
 나이가 0살 이하입니다.
 */

✅ 패턴 없이 정의

  • catch블럭에서 기본으로 제공하는 error 상수를 사용하여 블록 내부에서 구체적으로 정의
do{
    try print(checkingAge(age: 25))
}
catch {
    if let error = error as? ageError{
        switch error{
        case .maxAge:
            print("성인 입니다.")
        case .minAge:
            print("0살 이하힙니다.")
        }
    }
}


/*
 처리 결과
 성인 입니다.
 */

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

함수 안에 에러를 던지는 함수를 정의하여 사용하는 방식입니다.

해당 방식을 통해 다양한 형태의 축약 표현을 할 수 있습니다.

✅ 에러를 던지는 함수를 처리하는 함수 - 일반적인 정의

enum NameError: Error{
    case noName
}


// 에러를 던지는 함수 정의
func checkingName(name: String) throws -> String{
    if name.isEmpty{
        throw NameError.noName
    }
    else{
        return name
    }
}


//에러를 던지는 함수를 처리하는 함수 정의
func handleError(){   // 함수 내부에 do{ try } ~ catch{ }문 정의
    do{
        try print(checkingName(name: ""))
    }
    catch{
        print("이름이 없습니다.")
    }
}


handleError()


/*
 출력 결과
 이름이 없습니다.
 */

✅ 에러를 던지는 함수를 처리하는 함수 - throwing 함수로 에러 다시 던지기

enum NameError: Error{
    case noName
}


func checkingName(name: String) throws -> String{
    if name.isEmpty{
        throw NameError.noName
    }
    else{
        return name
    }
}


func handleError() throws{
    do{
        try print(checkingName(name: ""))
    }
//    catch{  // 생략 가능
//        print("이름이 없습니다.")
//    }
}

do{
    try handleError()   // 정상 작동시 handleError() 실행
}
catch {
    print("이름이 없습니다.")    // 에러 발생시 실행
}


/*
 출력 결과
 이름이 없습니다.
 */

✅ 에러를 던지는 함수를 처리하는 함수 - rethrowing 함수로 에러 다시 던지기

enum SomeError: Error {
    case aError
}


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


// 에러를 던지는 throwing 함수로 받는 함수를 파라미터로 받는 함수 정의
// rethrows -> 내부에서 다시 에러를 던지는 키워드
func someFunction1(callback: () throws -> Void) rethrows {
    try callback()             // 에러를 다시 던짐(직접 던지지 못함)
    // throw (X)
}


do {
    try someFunction1(callback: throwingFunc)
}
catch {
    print("에러 발생")
}

/*
 출력 결과
 에러 발생
 */

복잡한 에러 처리의 단점

위의 에러 처리 코드를 확인해보면 알 수 있듯이 처리 과정이 생각보다 복잡하고 귀찮습니다...

이러한 단점을 보완하고자 Swift5 이후에 나온 기능이... 바로 Result Type 문법이 되겠습니다.

profile
응애 나 코린이(비트코인X 코딩O)

0개의 댓글