오류처리(Error Handling)는 프로그램이 오류를 일으켰을 때 이것을 감지하고 회복시키는 일련의 과정입니다. 예를 들어 디스크의 파일을 읽어오는 기능을 구현한다고 하면, 파일이 존재하지 않을 수도 있으며, 읽기 권한이 없을 수도 있고, 잘못된 파일일 가능성도 있습니다. 이렇게 파일을 읽어오는 간단한 기능에서도 오류가 발생할 수 있는 가능성이 여럿 존재하고 오류처리 기능을 통해 이런 상황들을 구별하여 프로그램 자체적으로 오류를 해결할 수도 있고, 사용자와 상호작용을 통해 오류를 어떤 방향으로 풀어나갈지 제어할 수도 있습니다.
Swift에서 오류는 Error 프로토콜을 준수하는 타입으로 표현됩니다. 이 프로토콜을 준수하는 타입을 사용하여 오류를 정의하고, 이러한 오류를 던지고, 처리할 수 있습니다. Swift의 오류 처리 메커니즘은 do-catch 문, try, throw 키워드를 사용하여 구현됩니다. 다음은 Swift에서 오류를 표현하고 처리하는 방법에 대한 설명입니다.
오류를 정의하기 위해 Error 프로토콜을 준수하는 열거형을 주로 사용합니다. 예를 들어, 파일 읽기 오류를 정의해보겠습니다.
enum FileError: Error {
case fileNotFound
case unreadable
case encodingFailed
}
위 코드에서, Error 프로토콜을 채택함으로써 오류처리를 위한 타입임을 알 수 있습니다. 이렇게 오류의 종류를 미리 예상한 다음, 오류 때문에 다음에 행할 동작이 정상적으로 진행되지 않을 때라면 오류를 던져(Throw Error)주면 됩니다. 오류를 던져줄 때는 throw 구문을 사용합니다.
오류를 던질 수 있는 함수는 throws 키워드를 사용하여 정의합니다.
func readFile(at path: String) throws -> String {
let fileExists = false // 파일 존재 여부를 검사하는 로직 (예시로 false 설정)
if !fileExists {
throw FileError.fileNotFound
}
// 파일을 읽는 로직
// 만약 파일이 읽기 불가능하다면
throw FileError.unreadable
}
do-catch 구문을 이용하여 오류를 처리하려면 do 절 내부의 코드에서 오류를 던지면 catch 절에서 오류를 전달받아 적절이 처리해주면 됩니다.
let filePath = "/path/to/file"
do {
let fileContents = try readFile(at: filePath)
print("File contents: \(fileContents)")
} catch FileError.fileNotFound {
print("Error: File not found.")
} catch FileError.unreadable {
print("Error: File is unreadable.")
} catch {
print("An unexpected error occurred: \(error).")
}
오류가 발생할 가능성이 있는 코드에서 오류를 무시하고 싶다면 try?를 사용하여 nil을 반환하게 할 수 있습니다.
if let fileContents = try? readFile(at: filePath) {
print("File contents: \(fileContents)")
} else {
print("Failed to read the file.")
}
오류가 발생하지 않을 것이라고 확신할 수 있는 경우, try!를 사용하여 강제로 오류를 무시할 수 있습니다. 하지만, 오류가 발생하면 런타임 오류로 앱이 크래시될 수 있습니다.
let fileContents = try! readFile(at: filePath)
print("File contents: \(fileContents)")
defer 구문을 사용하여 현재 코드 블록을 나가기 전에 꼭 실행햐야 하는 코드를 작성해줄 수 있습니다. 즉, 오류가 발생하여 코드 블록을 빠져나가는 것이든, 정상적으로 코드가 블록을 빠져나가는 것이든 간에 defer 구문은 코드가 블록을 빠져나가기 전에 무조건 실행되는 것을 보장한다는 뜻입니다. 예를 들어, 함수 내에서 파일을 열어 사용하다가 오류가 발생하여 코드가 블록을 빠져나가더라도 파일을 정상적으로 닫아 메모리에서 해제해야 하기 때문에 defer 구문 내부에는 파일을 닫는 코드를 작성해주어 정상적으로 파일이 메모리에서 해제될 수 있도록 해야 합니다.
defer 구문은 꼭 오류처리 상황에서 사용해야 하는 것은 아니지만, 오류처리를 할 때 유용하게 쓰입니다. defer 구문은 오류처리 상황뿐만 아니라 함수, 메서드, 반복문, 조건문 등등 보통의 코드 블록 어디에서든 사용할 수 있습니다.
func someThrowingFunction(shouldThrowError: Bool) throws -> Int {
defer {
print("First")
}
if shouldThrowError {
enum SomeError: Error {
case justSomeError
}
throw SomeError.justSomeError
}
defer {
print("Second")
}
defer {
print("Third")
}
return 100
}
try? someThrowingFunction(shouldThrowError: true)
// First
// 오류를 던지기 직전까지 작성된 defer 구문까지만 실행됩니다.
try? someThrowingFunction(shouldThrowError: false)
// Third
// Second
// First
이처럼 스위프트는 오류를 처리하는 다양한 방법을 제공하며, 다른 기능과도 자유롭게 조합하여 사용할 수 있습니다.