Search code examples
swiftmemory-leaksuibuttonclosuresdeinit

memory leaking in custom button memory problems


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 }
}

Solution

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