[Swift] 에러 처리

임승섭·2023년 6월 30일
0

Swift

목록 보기
21/35
// 일반 함수 선언
func doSomething(num: Int) -> Bool {
	if num >= 7 {
    	return true
    }
    else {
    	return false
    }
}

// 에러를 정의 (열거형) -> 반드시 에러 프로토콜을 채택해야 한다
enum SomeError: Error {
	case aError
    case bError
    case cError
}

// 에러를 던지는 함수라면?
func doSomething(num: Int) throws -> Bool {
	if num >= 7 {
    	return true
    }
    else {
    	if num < 0 {		// 이 case에서 에러를 던져보자
        	throw SomeError.aError		// 에러 열거형 중 하나를 선택하자
        }
    	return false
    }
    
    // 정상적인 경우에는 true or false
    // 비정상적인 경우에는 에러
}

에러 처리 3단계

1. 에러 정의

enum HeightError: Error {
	case maxHeight
    case minHeight
}

2. 에러가 발생할 수 있는 함수에 대한 정의

  • throw 키워드
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. 에러가 발생할 수 있는 함수의 처리 (함수의 실행)

  • do, catch 블록, try 키워드
//checkingHeight(height: 160)	// 그냥 실행하려고 하면 에러


do {	// 정상적인 경우의 처리 상황 (true나 false 리턴하는 경우)
	var result = try checkingHeight(height: 160)	// 여기서 에러를 던지면 catch블록으로
    print("놀이기구 타는 것 가능 : \(result)")
} catch {
	print("놀이기구 타는 것 불가능")
}

에러 처리 방법 - try/try?/try!

정식 처리 방법 - try

do {
	let isChecked = try checkingHeight(height: 200)
    
    if isChecked {
    	print("청룡열차 가능")
    } else {
    	print("후룸라이드 가능")
    }
} catch {
	print("놀이기구 타는 것 불가능")
}

옵셔널 타입으로 리턴 - try?

  • 정상적인 경우 -> (정상) return 타입으로 return
  • 에러 발생 -> nil return
let isChecked = try? checkingHeight(height: 200)      // Bool?

// 사용할 때는 옵셔널 벗겨서 사용해야 한다!

Forced try - try!

  • 정상적인 경우 -> (정상) return 타입으로 return
  • 에러 발생 -> runtime error
let isChecked2: Bool = try! checkingHeight(height: 150)      // Bool

// 에러가 발생할 수 없다고 확신할 때만 사용해야 한다!

Catch 블럭 처리

  • switch문과 비슷한 느낌으로 에러 처리
/* 패턴이 있는 경우 */
do {
    let isChecked = try checkingHeight(height: 100)
    print("놀이기구 타는 것 가능: \(isChecked)")
    
} 
// 구체적인 에러를 매칭
catch HeightError.maxHeight  {    // where절을 추가해서, 매칭시킬 에러패턴에 조건을 추가할 수 있음
    print("키가 커서 놀이기구 타는 것 불가능")
} catch HeightError.minHeight {      // 생략가능 -> default 블록    
    print("키가 작아서 놀이기구 타는 것 불가능")   
}
// 모든 경우를 다 고려해줘야 함. 마지막에 catch만 쓰면 default



/* 패턴이 없는 경우 */
```swift
do {
    
    let isChecked = try checkingHeight(height: 100)
    print("놀이기구 타는 것 가능: \(isChecked)")
    
} 
// 구체적인 에러를 매칭하진 않았지만, error 상수 이용
catch {    // error 상수를 제공 (모든 에러가 넘어옴)
    print(error.localizedDescription)	// 그냥 error만 쓰는 것보다 이게 낫다
    
    // 타입 캐스팅 (다운 캐스팅) 가능
    if let error = error as? HeightError {    // 실제 우리가 정의한 구체적인 에러 타입이 아니고, 에러 타입(프로토콜)이 넘어올 뿐
        switch error {
        case .maxHeight:
            print("키가 커서 놀이기구 타는 것 불가능")
        case .minHeight:
            print("키가 작아서 놀이기구 타는 것 불가능")
        }
    }
}


/* Swift 5.3 Update */
do {
    
    let isChecked = try checkingHeight(height: 100)
    print("놀이기구 타는 것 가능: \(isChecked)")
    
} catch HeightError.maxHeight, HeightError.minHeight {   // 케이스 나열도 가능해짐
    
    print("놀이기구 타는 것 불가능")
    
}

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

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

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

// 에러의 처리
do {
    try throwingFunc()
} catch {
    print(error)
}

일반적인 함수로 처리

  • 함수 내부에서 do-catch문으로 에러 처리
func handleError() {
    do {
        try throwingFunc()
    } catch {
        print(error)
    }
}

handleError()

1. throwing 함수로 에러를 다시 던지기

  • 함수 내에서 에러를 직접 처리하지 못하는 경우, 에러를 다시 던진다
func handleError1() throws {// throws 키워드 반드시 필요
    //do {
    try throwingFunc()		// 에러를 다시 밖으로 던저버려
    //}                     // catch블럭이 없어도 에러를 밖으로 던질 수 있음 ⭐️
}

do {
    try handleError1()   // 에러를 받아서 처리 가능
} catch {
    print(error)
}

2. rethrowing 함수로 에러를 다시 던지기

  • 에러를 던지는 throwing함수를 파라미터로 받는 경우, 내부에서 다시 에러 던지기 가능
// 다시 에러를 던지는 함수(방법1)
func someFunction1(callback: () throws -> Void) rethrows {
    try callback()             // 에러를 다시 던짐(직접 던지지 못함)
    // throw (X)
}

// 다시 에러를 던지는 함수(방법2) - 에러변환		// 약간 어거지로 만든 예시 ㅇㅇ
func someFunction2(callback: () throws -> Void) rethrows {
    enum ChangedError: Error {	// 내부에서 또다른 에러를 다시 정의
        case cError
    }
    
    do {
        try callback()
    } catch {   // catch구문에서는 throw (O)
        throw ChangedError.cError    // "에러를 변환"해서 다시 던짐
    }
}

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

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

Defer 문

  • 할일을 미룬다
  • 코드의 실행을 스코프가 종료되는 시점으로 연기시킨다
/*Ex 1*/
func deferStatement1() {
    defer {
        print("나중에 실행하기")
    }
    print("먼저 실행하기")
}

deferStatement1()

/*Ex 2 - 주의*/
func deferStatement2() {    
    if true {
        print("먼저 실행하기")
        return
    }
    defer {                   // 디퍼문이 호출되어야, 해당 디퍼문의 실행이 예약되는 개념
        print("나중에 실행하기")
    }
}

deferStatement2()


/*Ex 3*/
// 등록한 역순으로 실행  ====> 일반적으로 하나의 디퍼문만 사용하는 것이 좋음
func deferStatement3() {
    defer {
        print(1)
    }
    defer {
        print(2)
    }
    defer  {
        print(3)
    }
}

deferStatement3()

/*Ex 4*/
for i in 1...3 {
    defer { print ("Defer된 숫자?: \(i)") }
    print ("for문의 숫자: \(i)")
}

0개의 댓글