0
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!

hydro1337x
  • 95
  • 5
  • 1
    The "self" being captured strongly in that closure appears to be the ViewModel, what is retaining the view controller other than the navigation controller that you are expecting to prevent it from being deallocated? – dan Nov 22 '22 at 16:43
  • I think your message gave me the incentive to think in the right way, could you verify if this is true: since state is a function it does not matter that inside of it self is captured strongly since the output of it would go to the ViewController which already holds the ViewModel strongly therefore it can deallocate properly. But let's say that state was not a function but a lazy var (lets omit the function argument). Then it would create a problem since it would create a retain cycle in the ViewModel because of which the ViewController would not be able to deallocate? – hydro1337x Nov 22 '22 at 20:12
  • No, making the state a lazy var would not be an issue either. – Daniel T. Nov 22 '22 at 20:18

1 Answers1

0

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.)

Daniel T.
  • 32,821
  • 6
  • 50
  • 72