[Swift] [Error Handling] Result Type

2025. 2. 3. 21:59·Swift/정리
728x90

ResultType In Swift

Swift에서 Result Type은 성공 혹은 실패로 끝날 수 있는 작업을 관리하기 좋은 Tool이다.

예를 들어 네트워크 요청을 하는 함수를 실행한다면 실행 결과가 성공시 요청한 데이터를 캡슐화하고 실패시 오류를 캡슐화할 수 있다.

Result Type을 사용하면 예상 경과를 보다 명확하게 정의할 수 있어 가독성과 유지보수성을 향상 시킬 수 있다.

그래서 Swift에서 오류 처리를 개선하기 위해 도입이 되었다고 한다.

Swift에서 Result Type은 성공과 실패 즉, 두가지 경우가 Enum 역할을 한다.

성공은 성공 시 기대되는 값이 되고, 실패는 발생하는 오류 값이 된다.

ResultType Syntax

Swift의 결과 유형은 기본적으로 해당 값으로 성공을 나타내거나 관련 오류가 있는 실패를 나타낼 수 있는 열거형이다. 이 라이브러리는 Swift 표준 라이브러리이다.

그러면 어떻게 사용하는지 확인해보자.

Result Type을 정의하려면 성공 시 가져야할 값과 실패시 포함할 오류 유형을 지정해야 한다. 코드로 확인해보자.

enum DataFetchError: Error {
    case networkFailure
    case dataCorrupted
    case unauthorized
}

// Define a Result type for fetching data
typealias FetchResult = Result<Data, DataFetchError>

위의 코드 예제를 확인해보면 작업이 성공할 때 예상되는 Data와 실패할때 예상되는 DataFetchError가 있다.

성공할대는 함수가 성공적으로 완료될 때 데이터에 접근할 수 있게되며, 실패할때는 어떤 유형의 오류가 발생하였는지 패턴 매칭(switch문)을 통하여 쉽게 구분하고 처리할 수 있다.

성공 및 실패 예제

아래 코드를 보며 이해해보자. 만약 아래 함수가 실패한다면 패턴매칭을 이용하여 failure가 실행될 것 이다.

성공한다면 가져온 데이터를 캡슐화하여 가져올 수 있다.

func fetchData(from urlString: String, completion: @escaping (FetchResult) -> Void) {
    guard let url = URL(string: urlString) else {
        completion(.failure(.networkFailure))
        return
    }

    // Simulate fetching data
    URLSession.shared.dataTask(with: url) { data, response, error in
        guard let data = data else {
            completion(.failure(.dataCorrupted))
            return
        }
        completion(.success(data))
    }.resume()
}

// Handling the result of fetchData
fetchData(from: "<https://example.com>") { result in
    switch result {
    case .success(let data):
        print("Successfully fetched \\(data.count) bytes.")
    case .failure(let error):
        switch error {
        case .networkFailure:
            print("Network failure - Please check your connection.")
        case .dataCorrupted:
            print("Data corrupted - Unable to process the data received.")
        case .unauthorized:
            print("Unauthorized - Access denied.")
        }
    }
}

장점

Result Type을 사용하여 에러핸들링을 하는 것은 성공 및 실폐 상황을 처리하기 위한 여러 이점이 있다.

  1. 명확성: 예상되는 성공과 실패 상태를 명확하게 확인할 수 있다.
  2. 안전성: 성공과 실패를 명시적으로 모두 처리하도록 강요함으로써 처리되지 않는 오류를 줄인다.
  3. 유연성: 동기화 및 비동기화 작업과 함께 사용할 수 있으며, 다양한 프로그래밍 환경에 다재다능하다.

잠재적인 오류가 있는 작업의 결과를 캡슐화하고 명확하게 정의할 수 있는 능력 덕분에 예측 가능하고 이해하기 쉬운 Swift 코드를 작성할 수 있다.

이 방식은 네트워킹, 데이터베이스 접근, 데이터 처리등 복잡한 개발에서 특히 유용하다.

함수에서 ResultType을 사용할 때

Swift에서 결과 유형은 네트워크 요청, 파일 작업 또는 실패할 수 있는 복잡한 계산과 같이 불확실한 결과를 가진 작업을 수행하는 함수에서 특히 유용한다.

이러한 함수를 정의할 때 성공 시 기대할 수 있는 데이터의 종류와 실패 시 발생할 수 있는 오류의 유형을 명확하게 나타내는 결과 유형을 반환하도록 지정할 수 있다.

예시

아래 코드를 보면 Result Type을 함수에서 어떻게 사용하는 지 확인할 수 있다.

주어진 URL에서 JSON 데이터를 로드하고 Parsing하는 기능을 갖는 함수이다.

어떠한 실패인지에 따라 오류값을 다르게 처리할 수 있다.

enum JSONError: Error {
    case invalidURL
    case networkError(Error)
    case parsingError(Error)
}

func loadJSON(fromURL urlString: String) -> Result<[String: Any], JSONError> {
    guard let url = URL(string: urlString) else {
        return .failure(.invalidURL)
    }

    do {
        let data = try Data(contentsOf: url)
        let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
        guard let dictionary = jsonObject as? [String: Any] else {
            return .failure(.parsingError(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Parsing failed"])))
        }
        return .success(dictionary)
    } catch let error as NSError {
        return .failure(.networkError(error))
    }
}

아래 코드를 보면 다양한 에러 처리를 손쉽게 철히라 수 있다.

코드의 가독성도 더 좋아지고 유지보수성도 향상된다.

func handleJSONResult(urlString: String) {
    let result = loadJSON(fromURL: urlString)

    switch result {
    case .success(let dictionary):
        print("JSON loaded successfully: \\(dictionary)")
    case .failure(let error):
        switch error {
        case .invalidURL:
            print("Invalid URL provided.")
        case .networkError(let networkError):
            print("Network error occurred: \\(networkError.localizedDescription)")
        case .parsingError(let parsingError):
            print("Error parsing the data: \\(parsingError.localizedDescription)")
        }
    }
}

// Usage example
handleJSONResult(urlString: "<https://api.example.com/data>")

장점

결과가 있는 함수를 사용하면 불확실한 값을 관리하는데 도움이 되고 모든 결과를 적절히 처리함으로써 안전성있는 애플리케이션을 만들 수 있다.

  1. 오류처리개선: 성공데이터와 오류를 동일한 유형으로 캡슐화하여 다양한 결과를 처리할 수 있다.
  2. 향상된 코드 가독성: 패턴매칭을 사용하여 성공 사례와 실패 사례를 명확하게 구분할 수 있다.
  3. 유연한 제어 흐름: 오류를 처리하는 방법과 시기를 제어할 수 있어 비동기 작업이 많은 프로그래밍에서 유연하다.

ResultType의 고급 기능들

성공한 데이터를 Map을 사용하여 손쉽게 변환하기

Swift의 결과 유형에는 매번 결과를 수동으로 풀지 않고도 변환하고 연산을 연쇄할 수 있는 맵 및 플랫맵과 같은 메서드가 포함되어 있다.

이러한 방법들은 특히 각각 잠재적으로 실패할 수 있는 일련의 작업을 처리할 때 결과를 간소화할 수 있는 것이다.

지도 방법은 오류 유형을 변경하지 않고 결과의 성공 값을 변환하는 데 사용된다.

결과가 성공하면 맵은 주어진 변환 함수를 성공 값에 적용합니다. 결과가 실패하면 변경 없이 오류를 통과한다.

예시

한번 아래 코드를 보면서 이해해보자.

아래 코드들을 보면 map과 flatmap을 이용하여 손쉽게 변환할 수 있다.

func fetchData() -> Result<Data, Error> {
    // Simulate fetching data
    return .success(Data())
}

let result = fetchData()
let stringResult: Result<String, Error> = result.map { data in
    return String(decoding: data, as: UTF8.self)
}

switch stringResult {
case .success(let string):
    print("Fetched string: \\(string)")
case .failure(let error):
    print("An error occurred: \\(error)")
}
func parseJSON(data: Data) -> Result<[String: Any], Error> {
    // Simulate parsing JSON
    return .success(["key": "value"])
}

let parsedResult = fetchData().flatMap(parseJSON)

switch parsedResult {
case .success(let dictionary):
    print("Parsed dictionary: \\(dictionary)")
case .failure(let error):
    print("Failed to parse JSON: \\(error)")
}

ResultType 함수를 여러개 사용하기

Result Type을 사용한다면 각각의 단계에서 실패할 수 있는 시퀀스를 쉽게 처리할 수 있다.

flatMap을 사용하면 각 단계가 이전 단계의 성공적인 결과에 따라 달라지는 작업을 수행할 수 있다.

아래 예제를 보면 flatMap이 이전 단계가 성공했을 때만 각 단계가 진행되는 논리적인 연산 체인이 가능하게 한다.

이전 단계가 실패하면 오류 처리가 됨으로 오류처리도 쉽게할 수 있다.

func downloadData(url: URL) -> Result<data, error=""> {
    // Simulate downloading data
    return .success(Data())
}

func parseData(data: Data) -> Result<[String: Any], Error> {
    // Simulate parsing data
    return .success(["data": "parsed"])
}

func processData(url: URL) -> Result<[String: Any], Error> {
    downloadData(url: url).flatMap { data in
        parseData(data: data)
    }
}

// Usage:
let url = URL(string: "<https://api.example.com>")!
let processResult = processData(url: url)
switch processResult {
case .success(let data):
    print("Processed data: \\(data)")
case .failure(let error):
  
</data,>

ResultType에 대한 오류 처리 방식

Swift의 결과 유형은 사용자 지정 오류 유형과 결합하여 던진 오류를 정확하고 유익하게 포착하고 처리할 수 있다.

사용자 정의 오류 유형을 정의하면 일반 오류를 사용하는 것보다 더 구체적으로 오류를 분류할 수 있어 오류 관리 관행의 견고성과 가독성을 향상시킬 수 있다.

예제

아래 네트워크 요청에 대한 오류처리방식을 봐보자.

프로필을 가져오는 동안 발생할 수 있는 다양한 오류들을 나열한다.

다양한 오류들을 효과적으로 처리할 수 있고 성공 및 실패 경로를 단일 유형으로 캡슐화하여 사용하기 때문에 더욱 안전한 구조를 만들 수 있다. 결과를 통해 데이터와 오류의 흐름이 명확해지고 예측이 가능한 오류 처리를 구조화 할 수 있다.

enum NetworkError: Error {
    case notConnected
    case notFound
    case unauthorized
    case unexpectedResponse(String)
}

func fetchProfile(userID: String) -> Result<String, NetworkError> {
    let profiles = ["001": "John Doe", "002": "Jane Smith"]
    guard let profile = profiles[userID] else {
        return .failure(.notFound)
    }
    return .success(profile)
}
let result = fetchProfile(userID: "003")

switch result {
case .success(let profile):
    print("Profile found: \\(profile)")
case .failure(let error):
    switch error {
    case .notConnected:
        print("Error: No network connection.")
    case .notFound:
        print("Error: Profile not found.")
    case .unauthorized:
        print("Error: User not authorized.")
    case .unexpectedResponse(let message):
        print("Error: Unexpected response - \\(message)")
    }
}

장점

  1. 명확성: 오류와 성공시 예상되는 데이터를 명확하게 정의할 수 있다.
  2. 유지보수성: 오류 처리 방식은 유지관리가 더욱 쉽다.
  3. 디버깅: 오류가 잘 분류되어 디버깅과 로깅이 더욱 간단해진다.

결론

Swift의 Result Type은 성공과 실패를 명확하게 처리할 수 있는 강력한 도구이다.

네트워크 요청, 데이터 처리, 오류 관리 등 다양한 시나리오에서 유용하게 활용할 수 있다.

Result Type을 사용하면 코드의 가독성이 향상되고, 오류 처리가 더욱 체계적이며 예측 가능해진다.

특히 map과 flatMap과 같은 고급 기능을 통해 데이터 변환과 연속적인 작업 처리를 효율적으로 수행할 수 있다.

또한, 사용자 정의 오류 타입과 결합하여 더욱 세밀한 오류 관리가 가능하며, 이는 애플리케이션의 안정성과 유지보수성을 크게 향상시킨다.

Result Type은 Swift에서 비동기 작업과 오류 처리를 더욱 안전하고 효과적으로 다룰 수 있게 해주는 필수적인 도구라고 할 수 있다.

728x90

'Swift > 정리' 카테고리의 다른 글

[Swift] WebView와 데이터 통신하는 두 가지 방법  (0) 2025.04.22
[SwiftUI] TCA(The Composable Architecture)이란?  (1) 2024.03.18
[SwiftDataStructure&Algorithms] 기본 데이터 구조(세트, 튜플)  (0) 2024.03.15
[SwiftDataStructure&Algorithms] 기본 데이터 구조(배열, 딕셔너리)  (0) 2024.03.13
[SwiftDataStructure&Algorithms] 데이터 구조  (1) 2024.03.12
'Swift/정리' 카테고리의 다른 글
  • [Swift] WebView와 데이터 통신하는 두 가지 방법
  • [SwiftUI] TCA(The Composable Architecture)이란?
  • [SwiftDataStructure&Algorithms] 기본 데이터 구조(세트, 튜플)
  • [SwiftDataStructure&Algorithms] 기본 데이터 구조(배열, 딕셔너리)
bulmang
bulmang
모바일 개발자 도전
  • bulmang
    bulmang
    bulmang
  • 전체
    오늘
    어제
    • 분류 전체보기 (208)
      • 알고리즘 (68)
        • List (3)
        • Two Pointer (6)
        • Binary Search (4)
        • Prefix Sum (3)
        • Sort (4)
        • Brute Force (5)
        • Array (2)
        • String (4)
        • 프로그래머스 (12)
        • 백준 (9)
        • Queue (2)
        • Stack (2)
        • Recursion (12)
      • Computer Science (16)
        • Computer Architecture (6)
        • Operating System (5)
        • Network (2)
        • 기타 (2)
        • System Programming (1)
      • Swift (70)
        • 개발 (24)
        • 정리 (25)
        • 문법 (20)
      • Flutter (24)
      • 기타 (12)
        • 후기 (12)
      • Git (6)
      • Ios 오픈소스 (5)
      • UI 디자인 (5)
      • AppleScript (2)
  • 링크

    • Notion
    • Github
  • 태그

    컴퓨터구조
    FLUTTER
    IOS
    자료구조
    피플
    Java
    코딩테스트
    백준
    today i learned
    Swift
    Xcode
    SwiftUI
    문법
    개발
    재귀
    riverpod
    til
    협업
    알고리즘
    Apple Developer Academy
  • 최근 댓글

  • 최근 글

  • 인기 글

  • 250x250
  • hELLO· Designed By정상우.v4.10.2
bulmang
[Swift] [Error Handling] Result Type
상단으로

티스토리툴바