프로그램은 항상 우리가 원하는대로 작동한다는 보장이 없다. 특히 복잡한 데이터나 자원을 많이 소비하는 작업의 경우 오류 발생 확률이 높아진다. 이에 대비하여 개발자는 예상 가능한 오류들을 적절하게 처리할 수 있어야 한다.
스위프트에서 오류는 Error 프로토콜을 준수하는 타입의 값으로 표현한다. Error프로토콜 자체에는 요구사항이 없지만, 오류를 표현하기 위한 타입(주로 enum)으로 Error 프로토콜을 채택한다.
열거형(enum)은 오류의 종류를 나타내기 아주 적합하다.
아래의 코드는 자동차 시동을 걸기 위한 함수가 던지는 에러의 종류를 표현한 CarIgnitionError 열거형이다.
enum CarIgnitionError: Error {
case keyMissing // 키가 없음
case noGasoline // 기름 없음
case batteryDepleted // 배터리 방전
}
Error 프로토콜을 채택하여 오류처리를 위한 타입이라는 것을 알 수 있다.
var isKey: Bool = true
var gasoline: Int = 30
var battery: Int = 0
func carIgnition() throws {
guard isKey else {
throw CarIgnitionError.keyMissing
}
guard gasoline >= 10 else {
throw CarIgnitionError.noGasoline
}
guard battery != 0 else {
throw CarIgnitionError.batteryDepleted
}
print("시동이 걸렸습니다.")
}
battery가 0 이므로 batteryDeplated 오류를 던진다.
try?와 try!를 사용하면 do-catch구문 없이 오류 처리를 간소화할 수 있다.
스위프트에서는 오류를 발생시킨 코드에서 직접 오류를 처리하는 것이 아니라, 이를 호출한 코드로 오류를 '전파'하여 처리하는 방식도 지원한다. 이를 통해 오류 처리를 더 적절한 위치에서 진행할 수 있으며 함수나 메소드의 역할을 더욱 명확하게 하여 코드의 가독성과 유지 보수성을 높일 수 있다.
enum FileError: Error {
case fileNotFound
}
func readFile(at path: String) throws -> String {
// 파일을 읽어서 문자열을 반환하는 코드 위치
// 만약 파일을 찾을 수 없다면, FileError.fileNotFound 오류 던짐
throw FileError.fileNotFound
}
func printFileContents() {
do {
let contents = try readFile(at: "/some/path")
print(contents)
} catch {
print("Failed to read file: \(error)")
}
}
readFile(at:) 함수는 파일을 읽는 역할만 하며, 오류를 던지고 오류를 처리하는 곳은 오류를 호출한 printFileContents()함수다. 이런 식으로 각 함수나 메소드는 자신의 역할에 집중할 수 있고, 오류 처리를 더 적절한 위치에서 진행할 수 있다.
Swift에서 오류 처리는 많은 문제들을 대처하도록 돕는다. 이는 Error프로토콜을 준수하는 오류 타입, throws 및 throw 키워드를 사용한 요류처리와 do-catch 및 try, try!, try? 키워드를 통한 오류 핸들링 등을 통해 이루어진다.
결국 오류 처리는 코드의 안정성을 향상시키고, 예상하지 못한 상황에 대비하며, 코드의 가독성과 유지 보수성을 높이는데 큰 역할을 한다.