5
votes

I found this UIBarButtonItem target issue unconsciously when using lazy var initialization.

class ViewController: UIViewController {
  lazy var barButtonItem1 = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(action1))
  lazy var barButtonItem2: UIBarButtonItem = {
    let barButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(action2))
    return barButtonItem
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    print(barButtonItem1.target, barButtonItem2.target)
  }
}

The printed results showed that barButtonItem1.target was nil, and barButtonItem2.target was self, which seems crazy! I got this issue when i use barButtonItem1's lazy var writing, and then i found that barButtonItem1's action can never be called, and finally the issue was barButtonItem1.target was nil.

I don't know why this happens, however i'm pretty sure this is a bug. Does anyone know something about this? I will really appreciate it if you can explain about it.

1
I don't think you're doing barButtonItem1 correctly. First lazy var must be declared with a data type. Second, you're just setting it's value like a regular var. Please read: hackingwithswift.com/example-code/language/… - thedp
The declaration of barButtonItem1 should be correct, according link. - 悟空之空
According to link, the declaration of barButtonItem1 should be correct. However i tried what you said to add a data type declaration for barButtonItem1, and it behaves well now. I still don't know what causes this. - 悟空之空
Anyway thank you for your comment! - 悟空之空
You should not implement the bar button like that. Why do you need this type of declaration? - Mannopson

1 Answers

3
votes

Explanation below is my guess. And unfortunately, I don't have enough reputation to give a comment so let me give you an answer.

My guess: this is a compiler bug.


First, I crafted a small extension of UIBarButtonItem. (second parameter is not of Any? but UIViewController?)

extension UIBarButtonItem {
    convenience init(barButtonSystemItem systemItem: UIBarButtonSystemItem, targetViewController: UIViewController?, action: Selector?) {
        // call the initializer provided by UIKit
        self.init(barButtonSystemItem: systemItem, target: targetViewController, action: action)
    }
}

Then I tried to initialize the lazy stored variable with the code below.

class ViewController: UIViewController {

    lazy var barButtonItem1 = UIBarButtonItem(barButtonSystemItem: .cancel, targetViewController: self, action: #selector(action))

    override func viewDidLoad() {
        super.viewDidLoad()
        print(barButtonItem1.target)
    }
    func action() { }
}

Then compiler raised error and say

Cannot convert value of type '(NSObject) -> () -> ViewController' to expected argument type 'UIViewController?'

which suggests that compiler failed to determine that self is of ViewController. (The initializer provided by UIKit would compile because the second parameter is of Any? which accepts value of type (NSObject) -> () -> ViewController.)

But when give type annotation to the lazy variable like

lazy var barButtonItem1: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, targetViewController: self, action: #selector(action))

source code happily compiled and barButtonItem1.target was set to self.

I believe the type annotation helped compile. Above is the reason I guess the issue you faced is caused by a compiler bug.


See also: there are reported problems similar to the issue you faced. Both of them are concluded as a compiler bug.

Swift lazy instantiating using self

Type inference when using lazy instantiation