이전까지 컴바인 프레임워크를 이용하면서 해왔던 에러처리들은 모두 업스트림에서 에러를 던져주면 다운스트임에서 .sink 를 통해 받기만 하는 수동적인 방법이였다. 하지만 만약 에러를 throw하는 메서드를 sink받기전에 쓰고 sink에서 catch하고 싶다면? 혹은 OOP개념과 함께 컴바인을 쓰면서 인터페이스로부터 나온 Stream을 받을 때 보통 추상화된 에러를 받기 때문에 sink로 받기전에 구체 에러타입으로 바꿔야한다면? .tryMap 연산자를 통해 에러처리를 할 수 있다.
enum APIError: Error {
case networkError
case dataCorrupted
case invalidFormat
}
struct User {
let id: Int
let name: String
}
// 예제 JSON 데이터
let jsonData = """
{
"id": 123,
"name": "Alice"
}
""".data(using: .utf8)!
// 추상화된 에러를 처리하고 데이터를 파싱하는 함수
func fetchData() -> AnyPublisher<Data, Error> {
// API에서 데이터를 가져오는 상황을 시뮬레이션
Just(jsonData)
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
private func parseUser(from data: Data) throws -> User {
guard let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []),
let dictionary = jsonObject as? [String: Any],
let id = dictionary["id"] as? Int,
let name = dictionary["nnnaaammme"] as? String else { // 일부러 에러발생
throw APIError.dataCorrupted
}
return User(id: id, name: name)
}
// 다운스트림에서 데이터 처리
func handleData() {
var subscriptions = Set<AnyCancellable>()
fetchData()
.tryMap { data -> User in
try parseUser(from: data)
}
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("Data processing completed successfully.")
case .failure(let error):
print("An error occurred: \(error)")
}
}, receiveValue: { user in
print("Received user: \(user)")
})
.store(in: &subscriptions)
}
handleData()
.tryMap 을 활용한 예제 코드이다. 살펴보면 fetachData() 메서드는 AnyPublisher<Data, Error> 라는 추상화된 Error 타입을 던져준다. 다운스트림에서는 업스트림에서 던져주는 Error를 구체화해서 처리해야한다. 이때 parseUser() 메서드는 구체환된 에러를 throw하고 있다. 이렇게 추상화된 에러와 구체화된 에러처리의 가교역할 및 throw 메서드를 sink로 받기전에 처리할 수 있는 연산자가 바로 tryMap 연산자이다. 소비자 입장에서 sink로 업스트림 밸류로 받기전에 먼저 tryMap을 통해 에러를 구체화해서 던져준뒤 sink를 통한 다운스트림에서 효과적으로 에러처리를 할 수 있다.
앞서 tryMap은 upstream에서 받은 데이터를 특정한 형식으로 변환하려고 시도할 때 사용된다면 mapError는 upstream에서 발생한 에러를 다운스트림에서 다룰 수 있는 다른 형태의 에러로 매핑(변환)할 때 사용된다. tryMap이 에러를 "바꾸는" 느낌이었다면 mapError는 말그대로 에러를 "맵핑"한다. 아래 예제코드를 들여다보자
// 네트워크 요청의 결과를 나타내는 enum
enum NetworkRequestError: Error {
case connectionError
case invalidData
case unknown
}
// 사용자에게 보여줄 에러
enum UserFriendlyError: Error {
case unableToConnect
case corruptedData
case somethingWentWrong
}
// 네트워크 요청을 시뮬레이션하는 함수
func performNetworkRequest() -> AnyPublisher<String, NetworkRequestError> {
// 실제 애플리케이션에서는 네트워크 요청 코드를 여기에 작성
Fail(error: NetworkRequestError.connectionError)
.eraseToAnyPublisher()
}
// 데이터 처리 및 에러 매핑
func handleRequest() {
let cancellable = performNetworkRequest()
.map { data in
// 데이터 처리 로직을 여기에 추가
print("Data received: \(data)")
}
.mapError { error -> UserFriendlyError in
// 네트워크 에러를 사용자 친화적인 에러로 매핑
switch error {
case .connectionError:
return .unableToConnect
case .invalidData:
return .corruptedData
case .unknown:
return .somethingWentWrong
}
}
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("Completed successfully.")
case .failure(let error):
print("An error occurred: \(error)")
}
}, receiveValue: { _ in
// 데이터를 성공적으로 처리한 경우 실행되는 블록
})
}
위 예제코드를 보면 tryMap은 에러를 throw하거나 추상화된 에러타입을 구체화된 에러타입으로 "변환"을 해주었다면 mapError는 에러 케이스별로 또 다른 에러와 "맵핑"을 해주고 있음을 확인할 수 있다. 이런 에러 맵핑을 보며 든 생각은 클린아키텍쳐 기준으로 각 레이어간 서로 다른 에러처리를 해주어야할 때 유용할 것 같다는 생각이 들었다.
24.05.15 추가
import Combine
let jsonData = """
{
"name": "John",
"age": 30
}
""".data(using: .utf8)!
let publisher = Just(jsonData)
.tryMap { data -> [String: Any] in
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
throw URLError(.badServerResponse)
}
return json
}
.sink(receiveCompletion: { completion in
if case let .failure(error) = completion {
print("Error occurred: \(error)")
}
}, receiveValue: { json in
print("Received JSON: \(json)")
})
컴바인의 에러처리에서 tryMap 연산자를 소개했지만 tryMap은 데이터 변환 작업에 중점을 두며, 에러 처리보다는 데이터 처리를 목적으로 사용된다. 이 과정에서 에러가 발생할 경우 throw로 처리해줄 수 있다. 만약 직접 이미 발생한 에러를 처리하고 스트림의 흐름을 변경하거나 대체 데이터를 제공하고자 할 때에는 cacth 연산자를 사용하자
https://www.kodeco.com/21773708-intermediate-combine/lessons/4
'Flutter' 카테고리의 다른 글
플러터 프로젝트 실행시 아이폰 실기기(or 시뮬)가 돌아가지 않는 문제 (0) | 2024.05.19 |
---|---|
| Combine | 12. Retrying and Catching Errors (0) | 2024.05.15 |
| Combine | 10. Managing Backpressure (0) | 2024.05.14 |
| Combine | 9. Networking with Combine (0) | 2024.05.14 |
| Combine | 8. Sequencing Operators (0) | 2024.05.13 |