Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS lazy var UIBarButtonItem target issue

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.

like image 903
思齐省身躬行 Avatar asked Oct 17 '22 12:10

思齐省身躬行


1 Answers

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

like image 127
Takanori Hirobe Avatar answered Oct 20 '22 23:10

Takanori Hirobe