본문 바로가기
Programming

Swift 오류 처리

by 나무수피아는 지식의 가지를 뻗어가는 공간입니다. 2025. 8. 3.
반응형

오류 처리 (Error Handling)

   이번 강의에서는 Swift에서 오류 처리 방법에 대해 상세히 배워보겠습니다. Swift는 안정적인 앱 개발을 위해 강력한 오류 처리 메커니즘을 제공합니다. try, catch, throws, do-catch 구문을 사용하여, 코드 실행 중 발생할 수 있는 오류를 예측하고 적절히 대처할 수 있습니다. 이 강의를 통해 여러분은 Swift의 오류 처리 기본 개념부터 실무에서 유용하게 쓰이는 다양한 기법까지 폭넓게 이해할 수 있을 것입니다.

1. 오류 처리 기본 구문

   Swift의 오류 처리는 do-catch 구문을 통해 이루어집니다. do 블록 안에 오류를 발생시킬 수 있는 코드를 작성하고, 오류가 발생하면 catch 블록에서 이를 잡아 처리합니다. catch는 여러 개를 둘 수 있어서 발생한 오류의 종류별로 맞춤 대응이 가능합니다.

enum FileError: Error {
    case fileNotFound
    case insufficientPermissions
}

func openFile(filename: String) throws {
    if filename == "missing.txt" {
        throw FileError.fileNotFound
    } else if filename == "restricted.txt" {
        throw FileError.insufficientPermissions
    }
    print("File opened successfully")
}

do {
    try openFile(filename: "missing.txt")
} catch FileError.fileNotFound {
    print("File not found error")
} catch FileError.insufficientPermissions {
    print("Insufficient permissions error")
} catch {
    print("Unknown error")
}

   위 예시에서 openFile 함수는 throws를 선언해 오류를 던질 수 있음을 알리고, 특정 조건에 따라 throw 키워드로 FileError 타입의 오류를 발생시킵니다. 호출부에서는 do-catch 구문으로 오류를 잡아내어, 각 상황에 맞게 메시지를 출력합니다.

2. 오류 정의 및 throw 키워드

   Swift에서 오류는 Error 프로토콜을 채택한 타입으로 정의합니다. 보통 enum 타입으로 만들어, 가능한 오류 상황을 명확히 분류하는 데 유리합니다. 오류를 발생시킬 때는 throw 키워드를 사용하며, 이때 함수는 반드시 throws 키워드를 명시해야 합니다.

enum CalculationError: Error {
    case divisionByZero
}

func divide(_ numerator: Int, by denominator: Int) throws -> Int {
    if denominator == 0 {
        throw CalculationError.divisionByZero
    }
    return numerator / denominator
}

do {
    let result = try divide(10, by: 0)
    print("Result: \(result)")
} catch CalculationError.divisionByZero {
    print("Cannot divide by zero")
} catch {
    print("Unknown error")
}

이 예시에서는 divide 함수가 0으로 나누기를 시도할 때 divisionByZero 오류를 던집니다. 호출부에서는 do-catch 구문으로 오류를 안전하게 처리합니다.

3. try, catch, throws 사용법

오류가 발생할 가능성이 있는 함수를 호출할 때는 try 키워드를 사용합니다. try는 오류가 발생하면 호출 위치로 오류를 던지고, do-catch 구문으로 처리하도록 돕습니다. throws 키워드는 함수 선언부에서 해당 함수가 오류를 던질 수 있음을 표시합니다.

func processFile(filename: String) throws {
    if filename.isEmpty {
        throw FileError.fileNotFound
    }
    print("File processed successfully")
}

do {
    try processFile(filename: "document.txt")
} catch {
    print("Error processing file")
}

위 코드는 빈 문자열이 들어오면 FileError.fileNotFound 오류를 던지고, 이를 do-catch에서 처리하는 모습입니다. 이런 방식을 통해 프로그램이 예외 상황에서도 안전하게 동작하도록 할 수 있습니다.

4. 간단한 오류 처리: try?, try!

Swift는 간단히 오류를 무시하거나 강제로 처리하는 방법도 제공합니다. try? 는 오류가 발생하면 nil을 반환해 실패를 표현하며, try! 는 오류 발생 시 프로그램을 즉시 종료시킵니다. 두 방식 모두 상황에 따라 적절히 선택해야 합니다.

// try? 사용 예시
let result = try? divide(10, by: 2)
print(result ?? "Error")  // 5

// try! 사용 예시
let result2 = try! divide(10, by: 2)
print(result2)            // 5

try?를 쓰면 오류 발생 시 nil이 되므로, 선택적 바인딩 등으로 쉽게 오류를 처리할 수 있습니다. 반면 try! 는 오류가 절대 발생하지 않을 것을 확신할 때만 사용하며, 오류 발생 시 앱이 크래시가 발생하므로 주의해야 합니다.

5. 여러 catch 절과 오류 바인딩

catch 절은 여러 개 작성 가능하며, 발생한 오류에 따라 각각 다르게 처리할 수 있습니다. 또한, catch 절에서 오류 객체를 변수로 바인딩하여 구체적인 정보를 얻을 수도 있습니다.

enum NetworkError: Error {
    case badURL(String)
    case timeout
    case unknown
}

func fetchResource(url: String) throws {
    if !url.hasPrefix("https://") {
        throw NetworkError.badURL(url)
    } else if url == "https://timeout.com" {
        throw NetworkError.timeout
    }
    print("Resource fetched successfully")
}

do {
    try fetchResource(url: "http://invalid-url.com")
} catch NetworkError.badURL(let url) {
    print("잘못된 URL: \(url)")
} catch NetworkError.timeout {
    print("네트워크 요청 시간 초과")
} catch {
    print("알 수 없는 오류 발생")
}

위 예시에서 catch NetworkError.badURL(let url) 구문은 오류 객체에서 URL 정보를 받아 구체적으로 처리합니다. 이런 방식으로 오류의 상세 내용을 활용할 수 있습니다.

6. 오류 전파와 throws 함수 체인

오류를 처리하지 않고 호출자에게 넘기는 것도 가능합니다. 함수가 오류를 처리하지 않으면 throws를 유지한 채 호출자에게 전달하고, 호출자가 do-catch로 처리하도록 합니다. 이를 통해 복잡한 오류 처리 로직을 계층적으로 구성할 수 있습니다.

func readConfig(filename: String) throws -> String {
    // 파일 읽기 중 오류 발생 가능
    if filename == "config.txt" {
        return "Configuration Data"
    } else {
        throw FileError.fileNotFound
    }
}

func loadApplication() throws {
    let config = try readConfig(filename: "missing.txt")
    print(config)
}

do {
    try loadApplication()
} catch {
    print("애플리케이션 실행 중 오류 발생: \(error)")
}

이 코드에서 readConfig가 오류를 던지면 loadApplicationthrows를 붙여 오류를 그대로 전달합니다. 최종적으로 호출한 do-catch 블록에서 모든 오류를 받아 처리합니다.

7. 사용자 정의 오류 타입 활용하기

오류 처리를 더 체계적으로 하려면, enum뿐 아니라 structclass를 활용한 사용자 정의 오류 타입도 만들 수 있습니다. 특히 오류에 부가적인 정보를 담거나, 복잡한 상태를 관리할 때 유용합니다.

struct ValidationError: Error {
    let message: String
}

func validateUsername(_ username: String) throws {
    if username.count < 3 {
        throw ValidationError(message: "아이디는 최소 3자 이상이어야 합니다.")
    }
    print("아이디가 유효합니다.")
}

do {
    try validateUsername("ab")
} catch let error as ValidationError {
    print("유효성 검사 실패: \(error.message)")
} catch {
    print("알 수 없는 오류")
}

이처럼 사용자 정의 오류 타입을 만들어 오류 메시지 등 상세 데이터를 함께 전달할 수 있습니다. catch에서 타입 캐스팅(as)을 통해 구체적 오류를 분기 처리할 수 있습니다.

8. 옵셔널과 오류 처리 비교

Swift에서는 오류 처리 외에도 Optional 타입을 사용하여 실패를 표현합니다. 하지만 오류 처리 구문은 실패 원인을 명확히 표현하고 복잡한 흐름 제어가 가능하다는 점에서 차별화됩니다.

  • 옵셔널은 값이 있거나 없음을 간단히 표현합니다. 실패 원인에 대한 정보는 제공하지 않습니다.
  • 오류 처리는 구체적인 실패 원인과 처리를 위한 분기 로직을 제공합니다.

따라서 단순한 실패 여부 판단엔 옵셔널이 적합하지만, 원인 분석 및 복잡한 흐름 제어가 필요할 땐 오류 처리 구문을 권장합니다.

9. 비동기 오류 처리 (Swift 5.5 이후)

Swift 5.5부터는 async/await 패턴에 오류 처리를 결합할 수 있습니다. async throws 함수는 비동기 작업 중 발생하는 오류를 호출자가 try await로 처리하도록 돕습니다.

enum NetworkError: Error {
    case serverError
}

func fetchData() async throws -> String {
    let success = false
    if !success {
        throw NetworkError.serverError
    }
    return "데이터"
}

Task {
    do {
        let data = try await fetchData()
        print("데이터 수신: \(data)")
    } catch {
        print("네트워크 오류 발생: \(error)")
    }
}

비동기 오류 처리 덕분에 네트워크 호출이나 파일 I/O 같은 비동기 작업도 간결하고 안전하게 관리할 수 있습니다.

10. 정리 및 팁

  • 오류 타입을 명확히 설계하여 각 오류 상황을 구분하세요.
  • do-catch 구문을 활용해 오류 발생 시 사용자에게 적절한 메시지를 전달하세요.
  • try? 는 간단한 오류 무시용, try! 는 오류 발생이 절대 없다고 확신할 때만 사용하세요.
  • 복잡한 함수 호출에서는 오류를 단계별로 전파하고 마지막에 처리하는 방식을 권장합니다.
  • Swift 5.5 이상의 프로젝트에서는 async throws 패턴으로 비동기 오류 처리를 익히세요.

Swift의 오류 처리는 안전하고 예측 가능한 프로그램을 만드는 데 핵심적인 요소입니다. 본 강의를 통해 기본 개념과 실전 활용법을 숙지하여, 여러분의 Swift 코드에 견고한 오류 처리 로직을 적용해 보세요!

반응형

'Programming' 카테고리의 다른 글

Swift 메모리 관리  (36) 2025.08.05
Swift 고급 타입  (56) 2025.08.04
Swift 확장과 접근 제어  (47) 2025.08.02
Swift 프로토콜  (37) 2025.08.01
Swift 클로저(Closure)  (55) 2025.07.31