Search code examples
iosswiftmemory-leaksclosuresrx-swift

Can you avoid closure memory leaks by using functions instead?


So I'm using RxSwift and has a function that looks like this:

private func setAndVerifyTestmode(isOn: Bool) {
    parameterService.setTestMode(value: isOn)
      .flatMap { _ in self.parameterService.requestTestMode() }
      .subscribe( { [weak self] _ in
        //do stuff })
      .disposed(by: disposeBag)
}

I noticed that I had forgotten to use [weak self] in the .flatMap so I added it like this:

private func setAndVerifyTestmode(isOn: Bool) {
    parameterService.setTestMode(value: isOn)
      .flatMap { [weak self] (_: Int?) in 
          guard let self = self else { return .just(nil) }
          self.parameterService.requestTestMode() }
      .subscribe( { [weak self] _ in
        //do stuff })
      .disposed(by: disposeBag)
}

But then it gave me an error: Generic parameter Result could not be infered

I couldn't get around it so I tried using a nested function instead of the closure, ending up with this:

private func setAndVerifyTestMode(isOn: Bool) {
    func requestTestMode(_: Int?) -> Single<Int?> {
      parameterService.requestTestMode()
    }
     
parameterService.setTestMode(value: isOn)
      .flatMap(requestTestMode(_:))
      .subscribe( { [weak self] _ in
        //do stuff })
      .disposed(by: disposeBag)
}

Great, the compiler was happy and it works. And in my world this takes care of the memory leak issues since I'm no longer using a closure requiring a reference to self. But, a collegue of mine told me that this is the exact same thing as not using [weak self] in a closure; and that you are still subjected to memory leaks using a nested function. I can't really see that they are the same thing since there isn't even a reference to self anymore.

Anyway, the question I have is: Do I get around the issue with memory leaks, self and [weak self] using the nested function above, or is my collegue right: it's the same thing; there is no gain to what I did?


Solution

  • But, a collegue of mine told me that this is the exact same thing as not using [weak self] in a closure; and that you are still subjected to memory leaks using a nested function.

    Your colleague is right. (This surprised me.)

    Fortunately, there are easier ways:

    The most obvious: .flatMap { [parameterService] _ in parameterService.requestTestMode() } By capturing parameterService directly instead of indirectly through self, you avoid capture of self.

    Another option: .flatMap { [weak self] _ in self?.parameterService.requestTestMode() ?? .just(0) } The value to emit is problematic because you used a Single so you have to emit something. If you had used a Maybe or Observable, you could have just used .empty()

    Like this: .asMaybe().flatMap { [weak self] _ in self?.parameterService.requestTestMode().asMaybe() ?? .empty() }

    Lastly you could use a free function (one that is not bound to any class or struct) that is curried:

    func requestTestMode(parameterService: ParameterService) -> (Int?) -> Single<Int?> {
        { _ in parameterService.requestTestMode() }
    }
    

    Which could be used like: .flatMap(requestTestMode(parameterService: parameterService)) And that is effectively the same as the first option I gave above.