Skip to content

[iOS] Concurrency와 Combine의 공존

TaeHyun edited this page Dec 12, 2023 · 9 revisions

✅ Concurrency + Combine

서비스를 구현하면서 CombineConcurrency의 사용에 대해서 고민하게 되었습니다.

팀원들과 같이 고민하다보니 Combine은 데이터 스트리밍을 조작하고 구독하는 것에 최적화 되어있다는 느낌을,
Concurrency 는 단발성 비동기 응답을 한 번 받는 것에 집중한다는 느낌을 받게 되었습니다.

그래서 이번 프로젝트에서는 Combine은 UI Binding에, Concurrency는 네트워크 통신에 활용하기로 했습니다.


🤔 언제 어떻게 변환하고 연결할 것이냐 ?

우리는 ViewModel에서 단방향 Flow를 구현하기 위해 Combine을 통해 Action → SideEffect → State의 스트림을 만들어놓은 상태였는데, 그래서 네트워크 통신에서 async로 받아온 데이터를 ViewModel에서 편하게 스트림으로 연결하려면 이를 Combine Publisher로 변환하는 작업이 필요했습니다.

Note

설계하는 과정에서 후보는 2가지가 있었습니다.

  1. ViewModel에서 UseCase에 async로 요청하고, 변환을 ViewModel에서 한다.
  2. UseCase에서 Publisher로 변환해 ViewModel에서 연결한다.
  • 1번 방법의 경우,
    UseCase → Repository → Network의 흐름이 모두 Concurrency로 이루어져 코드가 직관적이고, 깔끔하다는 장점이 있었지만
    ViewModel에서 변환하는 과정이 뎁스가 크고 사용하기 불편하다는 단점이 있었습니다.

  • 2번 방법의 경우,
    반대로 ViewModel에서 스트림으로 연결하는 작업이 깔끔하고 편했고, 데이터의 변환 (async → Combine) 역할을 UseCase로 분리할 수 있었습니다.


그래서 최종적으로 2번 방법으로 선택하기로 했고,
아래 그림처럼 UseCase에서는 데이터를 변환하고 비즈니스 로직을 적용하는 역할을,
Repository에게는 Domain-Data 사이의 데이터 Mapping과 데이터 소스에 접근하는 역할을 맡기게 되었습니다.


개인적으로는 Repository에게도 “~~데이터를 가져와줘!” 라는 단발성 비동기 요청을 하는 느낌이라 처음 생각했던 Concurrency의 역할에도 잘 맞는다고 생각이 들었고, UseCase에서 데이터 변환을 맡는 것도 우리 팀이 생각했던 역할 분담 내에서 자연스럽다는 느낌이 들었습니다!

ViewModel에서는 아래와 같이 응답 결과에 따라 SideEffect를 흘려보내줄 수 있었습니다. 🙂

extension HomeViewModel {
    func fetchHomeList() -> SideEffectPublisher {
        return homeUseCase.fetchHomeList()
            .map { travelList in
                return HomeSideEffect.showHomeList(travelList)
            }
            .catch { error in
                return Just(HomeSideEffect.loadFailed(error))
            }
            .eraseToAnyPublisher()
    }
}

다만, `Concurrency`에서 `Combine`으로 변환하는 작업 자체가 그다지 부드럽진 않은 것 같았어요.

예를 들어, ConcurrencyCombineFuture를 대체할 수 있다고 공식문서에서 얘기하는데 결국 Combine으로 변환하는 작업에서 Future를 사용하게 된다던지..


Clone this wiki locally