[Swift] Error Handling
Error Handling
에러 처리
- 컴파일 타임 에러
- 스위프트 문법과 관련된 에러
(컴파일러가 미리 알고 수정해야한다고 알려줌)
- 스위프트 문법과 관련된 에러
- 런타임 에러
- 프로그램이 실행되는 동안 발생
- 크래시(앱이 강제로 종료)
- 발생 가능한 에러를 미리 처리해 두면, 강제 종료되지 않음
(개발자가 처리해야 하는 에러)
- 발생 가능한 에러를 미리 처리해 두면, 강제 종료되지 않음
에러 처리가 필요한 이유
- 어떤 얘기치 못한 상황이 발생할 수 있음
- 네트워크 통신을 하는 등의 경우에서 서버에서 데이터를 못 받아와서 앱이 꺼짐
- 프로세스 중에서, 예외적인 상황(에러)이 발생하는 것이 미리 가능성 등을 처리해 놓으면 앱이 무작정 꺼지는 것을 예방 가능
- 에러를 통해 앱이 그냥 꺼지는 것 보다 알림을 통해 문제가 발생함을 유저에게 알려주는 것이 나음
- 에러는 일반적으로 함수의 처리과정에서 일어남
- 함수를 정의할때, 예외적인 일이 발생하는 경우가 발생할 수 있는 함수라고 정의하고 처리하는 과정이 에러 핸들링
에러 처리를 한 함수의 형태
- () throws -> ()
- (Int) throws -> ()
에러 처리 과정
// 1) 에러 정의 (어떤 에러가 발생할지 경우를 미리 정의)
enum HeightError: Error {//에러 프로토콜 채택 (약속)
case maxHeight
case minHeight
}
// 2) 에러가 발생할 수 있는 함수에 대한 정의
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) 에러가 발생할 수 있는 함수의 처리(함수의 실행)
// try와 do-catch문으로 처리
do {
// 함수 앞에 try를 붙여줘야함
let isChecked = try checkingHeight(height: 200)
print("놀이기구 타는 것 가능: \(isChecked)")
} catch {
print("놀이기구 타는 것 불가능")
}
// do 블럭 : 함수를 통한 정상적인 처리의 경우 실행하는 블럭
// catch 블럭 : 함수가 에러를 던졌을 경우의 처리 실행하는 블럭
에러 처리 방법
try
do {
let data = try parsing()
} catch {
// ...
}
- 에러 정식 처리 방법
- 모든 에러 발생의 예외적인 경우를 디테일하게 처리 가능
try? (Optional try)
let data = try? parsing()
- 결국 옵셔널 타입으로 리턴하기 때문에 언래핑 후 사용해야 함
- 정상적인 경우 : 정상 리턴 타입으로 리턴
- 에러가 발생 : nil 리턴
try! (Forced try)
let data try! parsing()
- 에러가 발생할 가능성이 없는 경우에 제한적으로 사용
- 정상적인 경우 : 정상 리턴 타입으로 리턴
- 에러가 발생 : 런타임 에러
- 에러가 발생할 수 없다고 확신이 있는 경우만 사용해야 함
catch 블럭 처리법
- catch 블럭은 do 블럭에서 발생한 에러만을 처리하는 블럭
- 모든 에러를 반드시 처리해야만 함
(글로벌 스코프에서는 모든 에러를 처리하지 않아도 컴파일 에러가 발생하지 않음)
1) 패턴을 모두 정의 (모든 에러 경우를 각각 따로 처리)
- 각 catch 블럭에서 모든 경우를 정의하거나, 구체적인 경우를 먼저 정의하고, 마지막엔 catch 키워드만 있어도 됨
(Swift 5.3 버전에서는 catch 뒤에 케이스 여러개 나열 가능)
// 상단 에러 처리 과정 3번 do-catch문 참고
do {
let isChecked = try checkingHeight(height: 100)
print("놀이기구 타는 것 가능: \(isChecked)")
} catch HeightError.maxHeight { // where절을 추가해서, 매칭시킬 에러패턴에 조건을 추가할 수 있음
print("키가 커서 놀이기구 타는 것 불가능")
} catch HeightError.minHeight { // 생략가능
print("키가 작아서 놀이기구 타는 것 불가능")
}
2) catch 패턴 없이 에러를 받아서 분기 처리로 처리
- catch 블럭 내에서 error 상수 제공
- error 상수(에러 프로토콜 타입)를 구체적으로 정의한 타입으로 캐스팅해서 스위치문 등으로 다시 처리
// 상단 에러 처리 과정 3번 do-catch문 참고
do {
let isChecked = try checkingHeight(height: 100)
print("놀이기구 타는 것 가능: \(isChecked)")
} catch { // error 프로토콜에서 error 상수를 제공 (모든 에러가 넘어옴)
print(error.localizedDescription)
// 실제 우리가 정의한 구체적인 에러 타입이 아니고, 에러 타입(프로토콜)이 넘어올 뿐
if let error = error as? HeightError {
switch error {
case .maxHeight:
print("키가 커서 놀이기구 타는 것 불가능")
case .minHeight:
print("키가 작아서 놀이기구 타는 것 불가능")
}
}
}
에러를 던지는 함수를 처리하는 함수
// 에러정의
enum SomeError: Error {
case aError
}
// 에러를 던지는 함수 정의 (무조건 에러를 던진다고 가정)
func throwingFunc() throws {
throw SomeError.aError
}
// 에러의 처리
do {
try throwingFunc()
} catch {
print(error)
}
- 함수 내부에서 에러 함수를 다루는 경우
- throwing 함수로 에러 다시 던지기
- 함수 내에서 에러를 직접 처리하지 못하는 경우, 에러를 다시 던질 수 있음
// 함수 내부에서 do-catch문으로 에러를 처리
// 발생한 에러를 catch블럭에서 받아서 알맞은 처리
func handleError() {
do {
try throwingFunc()
} catch {
print(error)
}
}
// do-catch블럭이 없어도 에러를 밖으로 던질 수 있음
func handleError1() throws {
//do {
try throwingFunc()
//}
}
do {
try handleError1() // 에러를 받아서 처리 가능
} catch {
print(error)
}
- 콜백 함수로 에러를 던지는 함수가 쓰이는 경우
- rethrowing 함수로 에러 다시 던지기
- 에러를 던지는 throwing 함수를 파라미터로 받는 경우, 내부에서 다시 에러를 던지기 가능
rethrows
키워드 사용 (rethrowing 메서드)
// 다시 에러를 던지는 함수(방법1)
func someFunction1(callback: () throws -> Void) rethrows {
try callback() // 에러를 다시 던짐(직접 throw 키워드로 던지지는 못함)
}
// 다시 에러를 던지는 함수(방법2) - 에러변환
func someFunction2(callback: () throws -> Void) rethrows {
enum ChangedError: Error {
case cError
}
do {
try callback()
} catch { // 파라미터로 사용된 함수만 catch절에서 제어 가능
throw ChangedError.cError // catch 구문에서는 throw 키워드로 다시 던지기 가능
}
}
// 실제 에러를 다시던지는(rethrowing)함수를 처리하는 부분
do {
try someFunction1(callback: throwingFunc)
} catch {
print(error)
}
do {
try someFunction2(callback: throwingFunc)
} catch {
print(error)
}
메서드/생성자에 throw 키워드 적용
- 에러는 throwing 함수 뿐만 아니라, 메서드/생성자에도 적용 가능
- 에러를 던질 수 있는 메서드는 throwing 메서드, 에러를 던질 수 잇는 생성자는 throwing 생성자
- 생성자/메서드 재정의
- throwing 메서드/생성자는 재정의 할 때, 반드시 throwing 메서드/생성자로 재정의 해야함
(throwing 메서드/생성자를 일반 메서드/생성자로 재정의 불가) - 상속 관계에서 범위 관계
- 일반 함수 > throwing 함수 > rethrowing 함수
- 상속 관계를 고려한 큰 범위에서 작은 범위로의 재정의는 가능
- throwing 메서드/생성자는 재정의 할 때, 반드시 throwing 메서드/생성자로 재정의 해야함