Language/Swift

[Swift] Result 타입

jaewpark 2023. 5. 12. 10:46

우선 Result 타입을 알아본 이유는 비동기 작업에서 에러 처리를 하다가 알게 된 키워드로 찾아보았습니다

 

Result

공식 문서

 

성공 또는 실패를 나타내는 값으로, 각 경우의 연관 값을 포함합니다

📌 Result 타입은 Swift 5 이전에 모든 오류를 전달 및 처리하는 데 유연성이 부족하여 하여 Swift 커뮤니티 내에서 일반적으로 사용하던 유형이었습니다. 더 복잡한 에러 처리 에러에 부합하지 않는 실패 값(즉, 비동기 작업)으로는 컴파일할 수가 없었기에 Result<Value, Error>로 단점을 해결하기 위해 추가 되었습니다
- SE-0235

 

실패할 수 있는 비동기 API 작성

공식 문서

 

실패할 수 있는 함수, 메서드 또는 기타 API를 작성할 때 선언에 throws 키워드를 사용하여 API 호출이 오류를 발생시킬 수 있음을 표시합니다. 그러나 비동기적으로 반환되는 API를 모델링하는 데는 throws 키워드를 사용할 수 없습니다. 대신, 비동기 호출의 성공 또는 실패 여부에 대한 정보를 캡처하려면 Result 열거형을 사용하고, 호출 결과에 대한 정보를 전달하려면 Result.success(*:) 및 Result.failure(*:) 케이스에 연결된 값을 사용하십시오.

let queue = DispatchQueue(label: "com.example.queue")

enum EntropyError: Error {
    case entropyDepleted
}

struct AsyncRandomGenerator {
    static let entropyLimit = 5
    var count = 0

    mutating func fetchRemoteRandomNumber(
        completion: @escaping (Result<Int, EntropyError>) -> Void
    ) {
        let result: Result<Int, EntropyError>
        if count < AsyncRandomGenerator.entropyLimit {
            // Produce numbers until reaching the entropy limit.
            result = .success(Int.random(in: 1...100))
        } else {
            // Supply a failure reason when the caller hits the limit.
            result = .failure(.entropyDepleted)
        }

        count += 1

        // Delay to simulate an asynchronous source of entropy.
        queue.asyncAfter(deadline: .now() + 2) {
            completion(result)
        }
    }
}
var generator = AsyncRandomGenerator()

// Request one more number than the limit to trigger a failure.
(0..<AsyncRandomGenerator.entropyLimit + 1).forEach { _ in
    generator.fetchRemoteRandomNumber { result in
        switch result {
        case .success(let number):
            print(number)
        case .failure(let error):
            print("Source of randomness failed: \\(error)")
        }
    }
}

 

Throwing 표현식의 결과 보존

공식 문서

 

값을 던지거나 반환할 수 있는 함수 호출 또는 기타 표현식의 전체 결과를 보존해야 하는 경우가 있습니다. 예를 들어 결과를 직렬화하거나 결과 데이터를 처리하는 앱의 다른 부분에 값으로 전달해야 할 수 있습니다. 이러한 시나리오에서 유형을 사용하여 Result잠재적으로 실패한 작업의 결과를 캡처하십시오.

 

일반적으로 do-catch문으로 즉시 처리하지만 때로는 일괄 호출 분석과 같은 작업 중에 나중에 처리하기 위해 작업의 전체 결과를 저장해야 합니다.

enum EntropyError: Error {
    case entropyDepleted
}

struct UnreliableRandomGenerator {
    func random() throws -> Int {
        if Bool.random() {
            return Int.random(in: 1...100)
        } else {
            throw EntropyError.entropyDepleted
        }
    }
}

나중에 통계 분석을 위해 일련의 호출을 저장하는 더 넓은 맥락에서 이니셜라이저를 사용합니다.

struct RandomnessMonitor {
    let randomnessSource: UnreliableRandomGenerator
    var results: [Result<Int, Error>] = []

    init(generator: UnreliableRandomGenerator) {
        randomnessSource = generator
    }

    mutating func sample() {
        let sample = Result { try randomnessSource.random() }
        results.append(sample)
    }

    func summary() -> (Double, Double) {
        let totals = results.reduce((sum: 0, count: 0)) { total, sample in
            switch sample {
            case .success(let number):
                return (total.sum + number, total.count)
            case .failure:
                return (total.sum, total.count + 1)
            }
        }

        return (
            average: Double(totals.sum) / Double(results.count - totals.count),
            failureRate: Double(totals.count) / Double(results.count)
        )
    }
}

 

 

Result의 사용

1. 타입 에러 (Typed Error)

enum LoadDataError: Error {
    case invalidURL
    case transportError
    case serverError
    case missingData
    case decodingError
}

URLSession.default.dataTask(with: url) { data, response, error in
	var result: Result<WantedData, LoadDataError>
	guard error == nil else {
		result = .failure(.transportError)
		return
	}
	...
}

 

2. Throw 처리

get

공식 문서

 

성공 값을 throw 식으로 반환합니다

func get() throws -> Success

이 메서드를 사용하여 성공을 나타내는 경우 이 결과의 값을 검색하거나 실패를 나타내는 경우 값을 catch합니다.

let integerResult: Result<Int, Error> = .success(5)
do {
    let value = try integerResult.get()
    print("The value is \\(value).")
} catch {
    print("Error retrieving the value: \\(error)")
}
// Prints "The value is 5."

 

3. 지연 처리 (Delayed Handling)

위에서 공식 문서의 예제를 확인하면 됩니다

 

 

4. Result 변형

Result는 map, flatmap, mapError 그리고 flatMapError 메서드가 존재합니다.

그 중에서도 mapError

 

mapError

공식 문서

 

주어진 변환을 사용하여 실패 값을 매핑하여 새 결과를 반환합니다.

func mapError<NewFailure>(_ transform: (Failure) -> NewFailure) -> Result<Success, NewFailure> where NewFailure : Error

Result 실패를 나타낼 때 인스턴스 값을 변환해야 하는 경우 이 방법을 사용합니다

 


참고 링크 1
참고 링크 2
참고 링크 3