본문 바로가기
이모저모/SwiftUI

TCA에서 Stream 생성 및 주입

by ARpple 2024. 4. 13.

원하는 구현 사항

  • RealmDB를 사용해서 타이머가 끝난 기록(Analyze)을 보여주는 기능
✅ 타이머가 끝나고 DB에 새로운 데이터가 추가되면, AnalyzeFeature에서 특별한 Action 없이 비동기로 새로운 데이터를 포함하는 목록을 AnalyzeState에서 가져오도록 만들고 싶었다.

구현 목표

도식화

기록 기능 구현 진행 사항

Analyze에서 새로운 타이머 기록 데이터를 Action 없이 가져오기

  1. RealmDB는 단일 Actor에서 작동하도록 설계되었고 (예외적인 사항 존재), 나는 메인 actor가 아닌 Realm전용 Actor에서 작동하도록 구현했다. ⇒ 이로 인해, async/await 비동기 처리를 필수로 만족해야함
  2. RealmDB을 접근을 Dependency로 분리했다.
    ⇒ Global한 값으로 TimerFeature와 AnalyzeFeature로 분리하기 위함이었다.

ReactorKit의 transform

Action이 아닌 외부 변화로 Action, Mutation, State 등을 트리거 하기 위한 도구

  • UIKit에서 많이 사용하는 단방향 Architecture인 ReactorKit은 위에 Dependecy와 같이 외부에서 발생하는 이벤트를 수신해서 처리하는 과정을 transform 메서드로 따로 처리할 수 있었다.
  • 이전 프로젝트에서 Transform을 사용한 예시
final class CHEditReactor: CHWriterReactor{
    ...
  weak var provider: ServiceProviderProtocol!
    ...
    // 1. provider: 외부 Dependency들의 객체를 담은 프로토콜
    // 2. chService: 채널과 관련된 Dependency를 구현한 프로토콜
    func transform(mutation: Observable<Mutation>) -> Observable<Mutation> {
        let chMutation = provider.chService.event.flatMap { [weak self] event -> Observable<Mutation> in
            switch event{
            case .update(let response):
                return Observable.concat([
                    .just(.isLoading(false)),
                    .just(.isClose(true))
                ])
            default: return Observable.concat([])
            }
        }
        return Observable.merge(mutation,chMutation)
    }
}
// 채널 서비스 프로토콜
protocol ChannelProtocol{
    var event:PublishSubject<CHService.Event> {get}
    ...
}

해결 방법

실패 - Combine의 PassthroughSubject를 사용하기

Reducer 구현부에서 event를 구독해서 해결하는 법

문제점

  1. 구독 sink시 async 클로져 처리 문제
  2. Feature는 struct 타입으로 cancellable을 사용할 수 없는 문제

성공 - Combine을 감싸는 AsyncStream 구현

AsyncStream | Apple Developer Documentation

An asynchronous sequence generated from a closure that calls a continuation to produce new elements.

developer.apple.com

비동기적으로 순서에 따라 클로져 내부에서 하고 싶은 일을 정의할 수 있음

  • Feature 구현
  • Dependecy 구현
final class AnalyzeRealmClient: AnalyzeAPIs{
    private var event = PassthroughSubject<AnalyzeEvent,Never>()
    ...
    func eventAsyncStream() -> AsyncStream<AnalyzeEvent> {
        return .init { [weak self] continuation in
            guard let self else{
                continuation.finish()
                return
            }
            let cancellable = self.event.removeDuplicates().sink { continuation.yield($0) }
            continuation.onTermination = { _ in
                cancellable.cancel()
            }
        }
    }
}

참고 자료

Swift Composable Architecture 를 도입하며 겪었던 문제와 해결

Swift Composable Architecture 를 도입하며 겪었던 문제와 해결법

안녕하세요 🖐️ 채널톡 iOS 엔지니어 우디입니다. 채널톡 iOS 팀에서는 SwiftUI를 적극적으로 활용하고 있는데요. 준비 중인 신규 서비스는 SwiftUI와 TCA를 사용해서 개발하고 있습니다. 이번 포스

channel.io

 

댓글