Why does this NOT leak memory? RxFeedback

51 views Asked by At
class ViewModel {
...
func state(with bindings: @escaping (Driver<State>) -> Signal<Event>) -> Driver<State> {
        Driver.system(
            initialState: .initial,
            reduce: State.reduce(state:event:),
            feedback:
                bindings,
                react(request: { $0.startLoading }, effects: { _ in
                  self.fetchFavoriteRepositoriesUseCase.execute()
                        .asObservable()
                        .observe(on: self.scheduler)
                        .map(self.repositoriesToRepositoryViewModelsMapper.map(input:))
                        .map { repositories in .loaded(repositories) }
                        .asSignal { error in
                            .just(.failed(error.localizedDescription))
                        }
            }))
    }
...
}
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let initialTrigger = BehaviorRelay<Void>(value: ())

        let trigger = Observable.merge(initialTrigger.asObservable(), refreshRelay.asObservable())

        let uiBindings: (Driver<FavoriteRepositoriesViewModel.State>) -> Signal<FavoriteRepositoriesViewModel.Event> = bind(self) { me, state in
            let subscriptions = [
                    state.drive(onNext: { state in
                        switch state {
                        case .initial:
                            print("Initial")
                        case .loading:
                            print("Loading")
                        case .failed:
                            print("Failed")
                        case .loaded:
                            print("Loaded")
                        }
                    })
                ]

            let events: [Signal<FavoriteRepositoriesViewModel.Event>] = [
                trigger.map {
                    .load
                }
                .asSignal(onErrorSignalWith: .empty())
            ]

            return Bindings(subscriptions: subscriptions, events: events)
        }

        viewModel.state(with: uiBindings)
        .drive()
        .disposed(by: disposeBag)
    }
}

I'm trying to grasp my head around why the react method from RxFeedback does NOT create a memory leak in this case. It has the effects closure as one of its arguments which is an @escaping closure and I'm not weakifying it, but capturing self strongly in it to call the use case. I assume it has nothing to do with RxFeedback but my knowledge of ARC and memory management.

To test the deallocation of the ViewController I'm just popping it from a NavigationController.

I would appreciate a detailed explanation on why this code is NOT creating a retain cycle. Thanks in advance!

1

There are 1 answers

1
Daniel T. On BEST ANSWER

There is no retain cycle. However, your view controller is holding several references (both direct and indirect) to your view model.

So for example, your view controller has a viewModel property. It's also holding a disposeBag which is retaining a disposable, which retains an Observable that retains the closure in your view model, which retains the view model.

The only time the strong capture of self is an issue is if the disposable is also being retained by the same object that is being captured. In this case, the view model is "self" but the view controller is the one retaining the disposable (through its dispose bag.)