# 에러처리 기초

func doSomething(num: Int) throws -> Bool {
    // 에러를 던지는 코드
    return true
}

함수에서 에러처리를 하기 위해서는 파라미터와 리턴타입 사이에 throws라는 키워드를 삽입해야한다. throws키워드를 삽입하게 되면 해당 함수는 에러를 반환할 가능성이 있는 함수라는 것을 의미한다.

스위프트 에러 정의는 열거형으로 한다. 이때 열거형 에러는 Error 프로토콜을 채택해야 한다.

enum SomeError: Error{
    case aError
    case bError
    case cError
}

func doSomething(num: Int) throws -> Bool {
    // 에러를 던지는 코드
    if(num > 0){
        throw SomeError.aError
    }
    return true
}

던진 에러에 대해 잡는 구문은 스위프트에서 do ~ catch 구문이다. 에러가 발생할 가능성이 있는 함수 앞에는 try라는 키워드를 꼭 붙여 호출해야 한다.

do {
    try doSomething(-1)
} catch {
    print("num은 음수입니다.")
}

# 에러처리 방법

do ~ catch에서 try로 에러 발생 가능성 함수를 호출하는 방법이 정석이고 더 간단한 방법으로 옵셔널 트라이 try?와 Forced 트라이 try!가 있다.

// 옵셔널 트라이
let isChecked: Bool? = try? doSomething(num: 3)

// Forced 트라이
let isChecked: Bool = try! doSomething(num: 3)

Forced 트라이는 에러 발생시 런타임 에러가 발생한다. 에러가 발생할 수 없는 경우에만 사용한다.

# catch의 처리

do {
    try doSomething()
} catch SomeError.aError {
    print("aError!!")
} catch SomeError.bError {
    print("bError!!")
} catch {
    // aError, bError를 제외한 나머지 에러들을 처리
    // switch의 default와 동일
}

자바스크립트의 try ~ catch와 동일하게 catch에는 error상수가 제공된다.

do {
    try doSomething()
} catch {
    // print(error) // 에러가 출력됨 -> 에러 열거형의 case가 출력
    // 일반적으로 에러 프로토콜의 localizedDescription이라는 속성을 출력한다
    print(error.localizedDescrption)
    // 열거형 case 타입 캐스팅 후 switch문에 전달하여 처리
    if let error = error as? HeightError{
        switch(error){
        case .aError:
            print("aError!!")
        case .bError:
            print("bError!!")
        }
    }

}

에러를 반환하는 콜백 처리

파라미터의 콜백 함수가 throws함수일때 바깥의 함수는 rethrows로 콜백으로부터 받은 에러를 다시 던져줘야 한다.

func someFunction1(callback: () throws -> ()) rethrows{
    try callback() // callback의 에러를 밖으로 던짐
}

do {
    try someFunction1(callback: throwingFunc)
} catch {
    print("CATCHED!!")
}

# 메서드 / 생성자 에러 처리

enum NameError: Error {
    case noName
}

class Course {
    var name: String

    // 에러를 던질 수 있는 생성자
    init(name: String) throws {
        if name == "" {
            throw NameError.noName
        } else {
            self.name = name
            print("수업을 올바르게 생성")
        }
    }
}

do {
    let _ = try Course(name: "스위프트5")
} catch NameError.noName {
    print("이름이 없어 수업이 생성 실패하였습니다.")
}

// ===== Course 상속 =====
class NewCourse: Course {

    // 부모클래스 Course의 생성자 오버라이딩
    // Course 생성자는 throwing 생성자이므로 try로 호출
    override init(name: String) throws {
        try super.init(name: name)
    }
}

# Defer문

func deferStatement(){

    defer {
        print("나중에..")
    }

    print("지금..")
}

deferStatement() // 지금...나중에...

defer문은 스코프가 종료되는 시점 마지막까지 미룬 뒤 실행하는 문장이다.

defer문은 반드시 한번은 실행이 되어야 예약이 된다!

한 스코프에 여러 defer문이 있으면 등록의 역순으로 실행된다. 스택에 쌓인 후 first - in - last - out 형태로 실행되는 느낌

for i in 1...3 {
    defer { print ("Defer된 숫자?: \(i)") }
    print ("for문의 숫자: \(i)")
}
// 함정이 있다
// for문의 숫자 1, Defer된 숫자 1 ...
// for문 루프 돌때마다 스코프가 종료되는 것

# Reference

  1. 인프런 - 앨런 swift 문법 마스터 스쿨 (opens new window)
  2. Side Table in Swift (opens new window)