0

Trying to figure out why the deinit is not called in OptionsButton class

func getActionButtonView(delegate: DiscoveryActionViewDelegate) -> UIView {
    switch delegate.actionType {
case .showVariants:
    let optionButton = OptionsButton(frame: CGRect(x: 0, y: 0, width: 60, height: 20))
          optionButton.setup()
          optionButton.selectOptionsAction = {
            delegate.showVariants(completionBlock: {  _ in
              optionButton.hideLoader()
            })
          }
          return optionButton
}

The BaseButton is the parent of the HardButton and HardButton is the parent of OptionsButton

class OptionsButton: HardButton {
  var selectOptionsAction: () -> Void = {}

  func setup() {
    setTitleColor(UIColor.color(227, 0, 77), for: .normal)
    backgroundColor = .white
    titleLabel?.font = UIFont.font(weight: .semiBold, style: .footnote1)
    setTitle("list_screen_selectOption_button".localized, for: .normal)
    addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
    borderColor = UIColor.color(227, 0, 77)
    progressColor = UIColor.color(227, 0, 77)
    borderWidth = 1
    cornerRadius = 4
  }

  @objc func buttonAction(sender: HardButton) {
    self.displayLoader()
    self.selectOptionsAction()
  }
    
    deinit {
        print("deinit OptionsBtn")
    }

}

Could anyone help me please the reason or suggest me what's wrong I did? where is/are the leak's

Edit 1-- more code:

enum DiscoveryActionType {
  case cartAction
  case showVariants
  case recommendationCartAction
}

protocol DiscoveryActionViewDelegate: AnyObject {
  func showVariants(completionBlock: @escaping (Bool) -> Void)
  var actionType: DiscoveryActionType { get }
}
Al-Zubair
  • 3
  • 2
  • A friendly observation: Next time, it would make it easier if you could omit irrelevant stuff (e.g., you undoubtedly could reproduce the problem without `setup` and all of the irrelevant extension methods that you’re calling there, make an example that manifested the problem with a simple subclass of `UIButton` rather than this undefined `HardButton`, etc.). You’ll make it easier for people to help you if you can distill it down to the bare minimum to reproduce the problem. See [MCVE](https://stackoverflow.com/help/mcve). – Rob May 12 '22 at 17:15
  • In an unrelated observation, you might consider calling `setup` in the `init` methods of `OptionsButton` so that the caller doesn’t have to do that. The question is whether you ever want to create a `OptionsButton` without calling `setup`. If not, have the `init` methods do that for you (and make `setup` a `private` method). It simplifies the calling point. – Rob May 12 '22 at 18:00

1 Answers1

0

You should confirm with “Debug memory graph”, but selectOptionsAction is a closure that has a reference to itself (and delegate, too). This is a classic “strong reference cycle”.

One can use weak references in the capture lists to break the strong reference cycle(s):

let optionButton = OptionsButton(frame: …)
…
optionButton.selectOptionsAction = { [weak delegate] in
    delegate?.showVariants { [weak optionButton] _ in
        optionButton?.hideLoader()
    }
}

You want to make sure that the button does not keep a strong reference to itself. I have also made sure to use a weak reference to delegate, too.

The details may vary, but hopefully this illustrates the idea. Use “debug memory graph” to identify what’s keeping a strong reference to the object in question and use weak references in your capture lists to break the strong reference cycles.


See this answer for example of how to use “Debug memory graph” feature.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • I've added more code. Calling func getActionButtonView(delegate from cellForRowAtIndexPatg – Al-Zubair May 12 '22 at 17:57
  • OK, then you want a weak reference to `delegate`. Revised answer above. – Rob May 12 '22 at 18:03
  • BTW, the above resolves the strong reference cycles that `OptionsButton` introduced. But if you’re still not seeing it released, look up higher in the view hierarchy to make sure its `superview` (or respective view controller) is deallocated, too. If you have other strong reference cycles higher up, that will also prevent the button from being released. But the “Debug memory graph” can be used to find strong reference cycles anywhere. It’s a great tool, once you get used to it. It is best used in a top-down fashion, looking for the highest level object that has not been deallocated properly. – Rob May 12 '22 at 18:18
  • 1
    [link](https://stackoverflow.com/questions/72196463/how-to-fix-memory-leak-from-debug-memory-graph-xcode) @Rob thanks a lot. I already asked the question. please do check – Al-Zubair May 12 '22 at 18:24