Optional이 값의 부재를 해결하고 Pass/Fail을 구분해주지만
에러 원인이 어느 케이스냐?를 알려주진 않는다
Representing and Throwing Errors
Error handling을 구현하려면 우선 Error를 나타낼 타입을 만들어야 한다
참고로 에러 케이스를 나타낼 타입은 열거형이 딱이다
그리고, "Error"라는 프로토콜을 채택해야 하는데
사실 이 프로토콜은 비어 있고 단지 indication 용이다
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int) //associated Value
case outOfStock
}
코드 Flow 도중 뭔가 예기치 않은 상황이 발생했다? → throw를 날려라
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
Handling Errors
자, 에러가 발생한 것 까진 인지했다. 음..
이제 어쩌지..?
Swift에선 4가지 ways가 있다
- (함수 내에서 발생 시) 함수를 호출한 곳으로 알리기
- do-catch 문으로 처리하기
- Optional로 처리하기
- Assert 걸기
각 항목에 대해선 아래에서 자세히 알아보자
우리는 에러가 발생하는 시점에 throw를 날린다는걸 알고 있다
덕분에 Error가 발생할 수 있는 코드위치를 어느정도 인지할 수 있다
근데 함수나 메소드 내에서 throw날리는거면? 알기가 빡세다...
그래서 try란 것도 만들었다 (try? / try! 도 있음)
이것도 차차 알아보자
Error Handling에 있어 Swift의 퍼포먼스 측면의 장점을 잠깐 짚고 넘어가자
(다른 언어에 비슷한 개념은 Exception handling이다)
그전에 Stack Unwinding이란 개념을 알아야 한다
다른 언어(아마 C++)에서 Exception Handling 시 stack을 unwind하게 된다
⁉️ Stack Unwinding 이란?
함수에서 throw 발생 시, 이동해야할 catch문(handler)이 존재하는 호출원을 찾으러 "Stack을 정리하면서" 역으로 타고 올라가는데 이 과정을 Stack Unwinding이라 한다
하지만 Swift에서는??
⁉️ Swift의 Error Handling은 stack unwinding을 하지 않는다
함수 내에서 throw를 날리면 현재 함수를 바로 종료시키고 호출원에서 처리하도록 맡긴다. 고로, Swift에선 throw와 return이 거의 유사하다
이게 가능한 이유는 do / try / throws 를 꼬박꼬박 명시해주기 때문이다
현재 함수에 throws가 선언되어 있는가? → 그럼 먼저 do가 있는지만 확인하고 없다면 바로 return해라
▫️Propagating Errors Using Throwing Functions
함수 외부로 Error 처리를 위임하려면 반드시 throws 선언을 해줘야 한다
⁉️ throws 라고 해서 무지성 위임 & return 하는게 아니다.
원한다면, do 구문을 넣어서 자기가 일부 처리하고 나머지를 위임할 수 있다
throws 선언된 함수일 경우, 처리되지 않은 case는 자동으로 caller로 위임된다
func nourish(with item: String) throws {
do {
try vendingMachine.vend(itemNamed: item)
} catch is VendingMachineError {
//VendingMachine 에러는 내가 처리해야지~
print("Couldn't buy that from the vending machine.")
}
}
//throws가 없는 main에서는 당연히 남은 모든 case를 커버해야 한다
do {
try nourish(with: "Beet-Flavored Chips")
} catch {
print("Unexpected non-vending-machine-related error: \(error)")
}
⁉️ initializer에도 throws를 쓸 수 있다
struct PurchasedSnack {
let name: String
init(name: String, vendingMachine: VendingMachine) throws {
try vendingMachine.vend(itemNamed: name)
self.name = name
}
}
▫️Handling Errors Using Do-Catch
가장 기본 handler인 do-catch문이다
case문이랑 동일하게 쓸 수 있다
케이스를 여러 개 하거나 where 사용가능
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
} catch pattern 3, pattern 4 where condition {
statements
} catch {
statements
}
▫️Converting Errors to Optional Values
나처럼 catch문을 짜는 것조차 귀찮은 게으른 사람들을 위한 try?가 있다
Error가 발생하면 handling하는게 아니라 그냥 nil을 return한다
아니 그럼, 그냥 Optional 쓰면 되잖아?
→ 이미 다른 사람이 throws로 잔뜩 구현해놨으면 이걸 다시 구현하긴 좀...
예시를 보자
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
//try?를 do-catch로 똑같이 구현하면 아래와 같다
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
이미 someThrowingFunction( )는 Error를 throw하는 함수로 구현되어 있다.
하지만 난 catch문에서 딱히 뭘 하고 싶지 않은데... 방법이 없을까?
싶을 때를 위해 try?가 있다
▫️Disabling Error Propagation
이런 경우가 있다.
이미 throws로 구현된 함수가 있긴한데..
이거 절대 runtime에 Error가 날 수 없음을 알고 있는 그런 상황
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
물론 잘못 썼다가 혹여나 Error를 throw하면 runtime error를 맞게 된다
Specifying Cleanup Actions
throw / return / break 에 의해 함수가 종료되는 상황에서..
어떤 식으로 종료되든 종료 전에 실행시키고 싶은 코드가 있다면?
함수가 종료되는 시점 이전에 defer로 원하는 코드를 묶어 적어놓으면
종료 직전에 수행되도록 등록시킬 수 있다
보통 File을 close한다던가 / 메모리를 해제하는 코드를 넣는 경우가 많다
그래서 소제목이 "Cleanup Actions"인 것
당연하지만, defer 안에는 return이나 break throw같은 flow를 제어하는 구문은 넣으면 안된다
⁉️ 참고로, defer 구문은 등록개념이라 여러 개를 작성하면 역순으로 실행된다
코드위치가 가장 아래에 있는 defer구문이 처음으로 실행된다
func abc() {
defer {
print("1")
}
print("zzzz")
defer {
print("2")
}
defer {
print("3")
}
return
}
abc()
//zzzz
//3
//2
//1
'Swift > Language Guide' 카테고리의 다른 글
Enumerations(열거형) (0) | 2021.09.16 |
---|---|
Closures (0) | 2021.09.15 |
Access Control - #1/1 (0) | 2021.09.13 |
Basic Operators - #1/1 (0) | 2021.09.13 |
Optional Chaining - #1/1 (0) | 2021.09.13 |
댓글