일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- OCP
- 명령-쿼리 분리
- 다형성
- 컴파일 타임 의존성
- 서브 타이핑
- 유여난 설계
- Swift#flatMap#map#Monad#함수형 프로그래밍#Optional
- 일관성 있는 협력
- 객체 생성 사용 분리
- 의존성
- 추상화
- 메서드를 통한 해결
- 런타임 의존성
- iSP
- 합성
- 상속 조합 폭발적 증가
- 상속
- '기존 설계 재사용
- OOP
- 알고리즘
- 객체지향
- 믹스인
- 책임주도설계
- dip
- 행동 호환성
- 하향식 접근
- 유연한 설계
- 설계 재사용
- 오브젝트
- Apple # HIG #iOS15 #iOS14 #Human #Interface #Guidelines #Apple developer # Apple human interface guidelines
- Today
- Total
도니의 iOS 프로그래밍 세상
Swift Error handling(try, catch) - Swift Concurrency(2) 본문
Try - Catch 공부배경
Swift Concurrency에서 가장 큰 장점중 하나는 에러처리입니다.
기존에 Result Type을 사용하거나, nil을 completion handler를 통해서 전달할 필요가 없어졌기 때문입니다.
단순히 async함수에 throws라는 키워드만 붙임으로써 보다 간단하고 실수없는 에러처리를 할 수 있습니다.
async 함수를 잘 적용하기 위해서 try catch가 언제 작동하며, 이들이 언제 에러를 전달하고 시스템 종료를 유발시킬수 있는지 알아야 합니다.
Swift Error Handling
에러 던지기(Throwing Errors)
Swift에서 Error는 Error Protocol을 준수하는 타입의 값으로 나타납니다. 해당 프로토콜을 채택하는 어떤 타입이든지간에 에러 throw가 가능해집니다. 간단한 에러를 처리할 땐, Error프로토콜을 채택한 Enumeration의 형태로 자주 쓰입니다
예시는 다음과 같습니다
enum CalculatorError: Error {
case dividedByZero
case overFlow
}
계산기 어플리케이션을 만든다고 했을때, 연산 에러가 날 가능성은 두가지입니다.
- 0으로 나누는 경우
- 연산 범위를 초과하는 경우
이러한 에러들을 throw를 쓰지 않고 어떻게 처리할 수 있을까요?
func divide(x: Int, y: Int) throws -> Int? {
if y == 0 {
return nil
}
return x / y
}
연산이 불가능하다고 표현을 하기 위해 Optional return을 하는 방법, -1을 지정하고 해당 값이 리턴됐다면 에러라고 인식하는 방법 두가지로 나뉩니다.
이는 옵셔널 래핑이나 리턴값이 특정한 값인지를 확인하는 별도의 작업이 필요합니다.
또한 다른 사람이 해당 코드를 보았을 때 에러의 원인을 인식하는 것 또한 어렵습니다.
이때 throw를 사용하면 어떻게 될까요?
func divide(x: Int, y: Int) throws -> Int {
if y == 0 {
throw CalculatorError.dividedByZero
}
return x / y
}
해당 함수는 자연스럽게 y가 0일땐 에러를 throw하는 함수라고 생각할 수 있게 됩니다.
에러 처리(Error Handling)
에러 상황 발생시, 에러를 리턴하는 방법에 대해서 배웠다면 지금은 리턴된 에러들을 처리하는 방법에 대해서 공부하겠습니다.
Swift에서 4가지 에러처리중, optional방식을 쓰지 않는 try로 throw 함수를 호출할때만 설명하겠습니다.(try?, try!는 설명에서 제외됩니다!
try로 호출시 에러처리는 do - catch 구문 사용과 throwing 함수를 이용한 Error를 전달하는 방식 두가지로 나뉩니다.
do - catch 구문 사용
do catch구문은 다음과 같습니다.
해당 코드블럭을 사용하여 do절에서 실행된 try에서 에러가 발생하면 catch에서 이를 처리하는 형태입니다.
아까의 계산기에서 나눗셈 부분을 호출하겠습니다.
func normalFunction() {
do {
let result = try divide(x: 30, y: 0)
print(result)
} catch CalculatorError.overFlow {
print("overflow 발생")
} catch CalculatorError.dividedByZero {
print("0으로 나누는 경우 발생")
} catch {
print("default Error Case")
}
}
do catch 구문을 통해서 divide함수가 정상적으로 실행되었다면 다음 print함수를 출력하게 됩니다.
만약 에러가 발생하였다면, Catch 구문으로 들어가게 됩니다. 그리하여 0으로 나눴기 때문에
“0으로 나누는 경우 발생”이 호출되게 됩니다.
throwing function
throws라는 키워드를 붙이게 되면 에러 전달이 가능한 “throwing function”입니다.
해당 함수는 발생 에러를 호출된 범위로 전파하는 역할을 합니다. throwing function은 에러 전파만 가능하며, throws키워드가 붙지 않은 함수 내부에서 발생한 에러는 반드시 “함수 내부”에서 처리가 되어야합니다.
그렇지 않으면 에러로 인해 프로그램 종료를 발생시킵니다.
에러 전파의 예시를 살펴보겠습니다
enum ErrorTest: Error {
case minusValue
case zeroValue
}
func positiveValue(number: Int) throws -> Int {
if number > 0 {
return number
} else if number == 0 {
throw ErrorTest.zeroValue
} else {
throw ErrorTest.minusValue
}
}
func throw_function_test() throws {
let positiveNumber = try positiveValue(number: 3)
print(positiveNumber)
let zeroNumber = try positiveValue(number: 0)
print(zeroNumber)
let negativeNumber = try positiveValue(number: -3)
print(negativeNumber)
}
func testFunction() {
do {
try throw_function_test()
} catch ErrorTest.minusValue {
print("minus Value")
} catch ErrorTest.zeroValue {
print("zero Value")
} catch {
print(error.localizedDescription)
}
}
testFunction이라는 함수를 호출하게 됐을때 결과는 어떨까요?
출력결과 다음과 같습니다.
3
zero Value
해당 결과가 나온 원인은 throw_function_test함수에서 positiveValue함수를 0을 parameter로 가진채 호출했기 때문입니다.
그리하여 postivieValue에서 zeroValue라는 에러가 전파되고, 이를 throwing_function_test에서 다시 zeroValue를 최상단 함수까지 전파한 것입니다. testFunction은 일반함수이기 때문에 해당 함수 내에서 에러를 반드시 처리해야 합니다. 따라서 do-catch구문을 통해서 반드시 에러를 해결해야 합니다.
하지만 throwing함수에서 do-catch를 사용할수 없을까요?
⇒ do catch를 통해서 throwing함수또한 내부에서 에러를 처리하는 게 가능합니다.
예시는 다음과 같습니다
func throw_function_test_return_throws () throws -> Int {
do {
let positiveNumber = try positiveValue(number: 3)
print(positiveNumber)
let zeroNumber = try positiveValue(number: 0)
print(zeroNumber)
let negativeNumber = try positiveValue(number: -3)
print(negativeNumber)
return negativeNumber
} catch {
return 3
}
}
결국 throwing함수는 에러전파를 별도의 처리없이도 할 수 있습니다.
하지만 do-catch를 쓰지 않고도 Depth가 깊은 함수의 경우 모든 단계마다 에러처리를 해줄 필요가 없게됩니다. 결국 최하단의 에러가 최상단까지 전파되어 에러 원인을 파악할 수 있게 됩니다. 그리하여 throwing함수에서는 do-catch보단 해당 에러를 전파시켜 디버깅시에 원인파악을 더 빠르게 하는게 좋은 방법이 아닐까 합니다.
결론
- throws 키워드가 붙지 않은 함수는 반드시 함수내에서 do - catch를 사용해 에러를 처리해야 합니다
- throws함수는 throwing함수를 호출할 때 do-catch를 사용하지 않아도 Error가 전파되기 때문에 사용할 필요가 없습니다.
- throws함수는 최하위 함수에서 에러가 발생하더라도, 에러 전파로 최상단 함수까지 전달된다. 그리하여 원인 파악이 더욱 간편하다.
- throws 키워드가 붙은 함수라도 do-catch를 사용해서 에러처리를 별도로 하는것또한 가능하다.
'Swift' 카테고리의 다른 글
Group Task - Swift Concurrency(4) (0) | 2023.03.20 |
---|---|
Async let - Swift Concurrency(3) (0) | 2023.03.09 |
Swift Concurrency(1) - 기본개념 및 작동원리 (0) | 2023.02.08 |
[Swift] ARC weak self를 하는 이유(delayed deinitialization, memory leak) - ARC 응용편 5탄 (0) | 2023.01.15 |
[Swift] ARC와 클로저, 클로저 memory leak - ARC 기초편 4탄 (0) | 2023.01.11 |