SwiftUI ) TCA 단점 ( with AsyncStream, cancel, cancelable ) - EEYatHo iOS
TCA의 단점
WebSocket 을 구현하는 등,
Stream을 받아와서 구독하고, 지속적으로 이벤트를 수신해야할 때 RetainCycle 이 무조건 발생한다.
즉, view의 사이클에 맞춰서 명시적으로 구독을 해제해줘야 한다.
이유
Action을 발생할 수 있는 엔드포인트를 .run을 통해 받아오고,
Stream의 실행부에, 엔드포인트를 넣기 때문에 RetainCycle이 발생한다.
( 스트림실행부 -> 엔드포인트 -> 리듀서 -> 스트림 -> 스트림실행부 )
( Class의 weak가 그립다.. )
결과
때문에, view의 사이클에 맞춰서 명시적으로 구독을 해제해줘야, reducer도 잘 해제된다.
( onAppear, onDisappear 을 활용. )
( 만약 init, deinit 으로 하고싶다면, class로 DeinitDetector를 만들어서 사용. )
Rx나 Combine으로 시도
Rx/Combine의 체이닝 마지막에 넣는 diposeBag/cancelables를 사용할 때 문제가 발생.
Rx, Combine 공통 제약사항
diposeBag/cancelables 의 선언 위치는 리듀서의 프로퍼티가 아닌, State안에 넣어야함.
( 리듀서는 struct이고, reduce 함수는 mutating func가 아니므로, 리듀서의 프로퍼티를 변경할 수 없음. )
( 때문에, 명시적으로 해제하기 위해서는 State 안에 넣어야함 )
Combine의 제약사항 -> 불가능
cancelables는 퍼블리셔의 store에 넣기위해서는 mutable해야하는데,
mutable한 채로 .run 클로저 안에 가져올 수 가 없음..
Rx의 제약사항 -> 코드가 더러워짐
1. disposeBag는 Equatable를 따르지 않기에 State에 넣으면 == 함수를 구현해야함.
2. 엔드포인트가 Sendable라서 실행부 내부를 Task 로 감싸야함.
답은 AsyncStream -> Best Practice!
Task를 State에 들고있고, 수동으로 해제했지만 사실 그럴필요도 없음.
이를 지원하는 Effect가 존재하기 때문.
( cancel, cancelable )
자세한 구현은 TCA 깃헙의 Example 중 웹소켓 구현 부분을 참고
이걸 처음부터 알았다면 내 하루를 아낄 수 있었을텐데..