Search code examples
swiftuikit

what is the difference between addAction and addTarget


I was thinking about button click events and we have different options

  • UITapGestureRecognizer
  • addTarget
  • addAction

for a basic operation let's say

button.addTarget(self, action: #selector(buttonClicked), for: .touchUpInside)

@objc private func buttonClicked () {
    print("button clicked")
}

however we can do the same thing without going through objc functions or selectors using the following

extension UIControl {
    func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping()->()) {
        addAction(UIAction { (action: UIAction) in closure() }, for: controlEvents)
    }
}

button.addAction(for: .touchUpInside) { [weak self] in
      guard let self = self else {return}
      self.buttonClicked()
}

func settingsClicked () {
    print("settings clicked")
}

I took a look at apple's documentation for addAction and could not find much there and I was wondering the difference between the two and which one should I use more


Solution

  • The addTarget(_:action:for:) approach uses target/action, the old Objective-C based dynamic dispatch approach where you provide a target object and a selector.

    If you are targeting iOS ≥14, you can use the newer addAction(_:for:) which allows you to provide a UIAction object, which includes a closure. It's more modern, but won't work for iOS <14.

    Closures and memory:

    One thing to be aware of with closures is that there is the potential to create a "retain cycle" and possible memory leak.

    If your closure references instance variables from self, that causes the closure to hold a strong reference to self.

    IBAction closures are "escaping" closures, which means that they persist. The system retains the closure, and the closure can retain the object that defines it (often a view controller.) That can lead to retain cycles and memory leaks.

    The way to avoid closures capturing objects is with a "capture group". If your closure is retaining "self", you'd add a capture group that causes self to be referenced weakly. ([weak self])

    That might look like this:

    let action = UIAction(title: "Tap Me", handler: { [weak self] theAction in
        print("Tapped on \(String(describing: theAction.sender))");
        self?.doSomething(theAction); // Self is now optional and won't be retained.
    });
    let button = UIButton(configuration: .filled(), primaryAction: action);