[Swift] 예외 처리

팔랑이·2024년 5월 31일

iOS/Swift

목록 보기
30/83
post-thumbnail

프로그래밍을 하다 보면 예상치 못한 오류로 프로그램이 갑자기 종료되는 등의 에러가 발생한다. 예외 처리란, 이런 코드 실행 중 발생할 수 있는 오류나 예외 상황을 관리하는 것을 말한다.

네트워크 장애, 파일 입출력 오류, 사용자 입력 오류 등 다양한 원인으로 오류가 발생한다. 따라서 가능한 모든 오류 상황을 사전에 예측하고 완벽하게 코딩하는 것은 불가능에 가깝고, 그렇기 때문에 예외 처리를 사전에 제대로 해 놓는 것이 중요하다. 잘 정의된 예외 처리는 코드의 가독성을 높이고, 디버깅과 문제 해결을 쉽게 만들어준다.

스위프트에서 예외 처리는 do, try, catch 구문을 사용하여 런타임 오류를 처리하는 방식으로 구현된다.


기본 개념

  1. Error 프로토콜: 스위프트에서 오류를 나타내는 타입은 Error 프로토콜을 준수한다.
  2. Throwing 함수: 오류를 던질 수 있는 함수나 메서드는 throws 키워드를 사용하여 선언된다.
  3. do-catch 구문: 오류를 처리하기 위해 do, try, catch 구문을 사용한다.

1. Error 프로토콜 사용한 오류 타입 정의

enum MyError: Error {
    case invalidInput
    case networkError
    case unknown
}

Error 프로토콜을 준수하는 MyError 열거형을 정의했다. 이렇게 하면 다양한 오류 상태를 열거형으로 표현할 수 있다.

2. 오류를 던질 수 있는 Throwing 함수 정의

func riskyFunction(value: Int) throws -> String {
    if value < 0 {
        throw MyError.invalidInput
    } else if value == 0 {
        throw MyError.networkError
    } else {
        return "Success"
    }
}

riskyFunction 함수는 throws 키워드를 사용하여 오류를 던질 수 있다고 선언되었다. 함수 body에 서술된 특정 조건에서 오류를 던질 수 있고, 정상적인 경우에는 문자열을 반환한다.

3. do-catch 구문을 사용한 오류 처리

do {
    let result = try riskyFunction(value: -1)
    print(result)
} catch MyError.invalidInput {
    print("Invalid input provided.")
} catch MyError.networkError {
    print("Network error occurred.")
} catch {
    print("An unknown error occurred: \(error).")
}

위 예제에서는 do 블록 안에서 try 키워드를 사용하여 riskyFunction 함수를 호출한다. 만약 오류가 발생하면 catch 블록에서 해당 오류를 처리

예제: 네트워크 요청의 오류 처리

enum NetworkError: Error {
    case badURL
    case requestFailed
    case unknown
}
// Error 프로토콜 준수하는 오류 열거형 정의
// NetworkError는 badURL, requestFailed, unknown 세 가지 케이스를 가진다.

func fetchData(from urlString: String) throws -> Data {
    guard let url = URL(string: urlString) else {
        throw NetworkError.badURL
    }
	// throws 키워드를 이용해 오류를 던질 수 있음을 나타냄
    // urlString을 받아서 URL로 변환을 시도
    // 정상적인 경우, 'Data' 객체 반환
    // 실패하면 badURL 오류 throw


    // 간단한 예시를 위해 URLSession 사용 생략
    // 실제 네트워크 요청을 처리하는 코드는 비동기로 작성
    let data = Data() // 네트워크 응답 데이터
    return data
}

do {
    let data = try fetchData(from: "invalidURL")
    print("Data received: \(data)")
} catch NetworkError.badURL {
    print("Bad URL provided.")
} catch NetworkError.requestFailed {
    print("Request failed.")
} catch {
    print("An unknown error occurred: \(error).")
}

// do 블럭에서 try를 사용해 fetchData 함수 호출
// 함수 실행 중 오류가 발생하면 적절한 catch 블럭에서 오류 처리

참고: do - try catch 구문의 기본 구조

do {
    try someFunction()
    // try 키워드를 사용하여 오류를 던질 수 있는 함수를 호출
    // 이 구문 안에 더 많은 코드를 포함할 수 있음
} catch SomeError.errorCase1 {
    // 특정 오류를 처리하는 코드
    print("Error case 1 occurred.")
} catch SomeError.errorCase2 {
    // 다른 특정 오류를 처리하는 코드
    print("Error case 2 occurred.")
} catch {
    // 위에서 처리되지 않은 모든 오류를 처리하는 코드
    print("An unknown error occurred: \(error).")
}

rethrows 키워드

rethrows 키워드는 특정 함수가 자신이 호출하는 클로저에서 발생한 오류만 던질 수 있도록 할 때 사용한다. 즉, rethrows 함수 자체는 오류를 던지지 않지만, 인자로 받은 클로저가 오류를 던질 때 사용할 수 있다.

func someFunction<T>(_ value: T, using closure: (T) throws -> Void) rethrows {
    try closure(value)
}

위 함수는 value값과 오류를 던질 수 있는 클로저를 인자로 받는다. rethrows 키워드를 통해 클로저에서 발생한 오류를 다시 던질 수 있다.

예제

func process<T>(_ value: T, using closure: (T) throws -> Void) rethrows {
    try closure(value)
}

do {
    try process("Test") { text in
        print(text)
        throw MyError.invalidInput
    }
} catch {
    print("Error occurred: \(error).")
}
profile
정체되지 않는 성장

0개의 댓글