ccoroom
ccoroom

Reputation: 689

Swift - `self` in variable initialization closure

How does button.addTarget(self, action: #selector(taptap), for: .touchUpInside) work without a lazy keyword?

Dropped lazy by mistake, and have a closure to initialize a button like below:

class MyView: UIView {

    let button: UIButton = {
        let button = UIButton()
        button.addTarget(self, action: #selector(taptap), for: .touchUpInside)
        print(self) // (Function)
        // button.frame = bounds <- Cannot assign here
        return button
    }()

    lazy var button2: UIButton = {
        let button = UIButton()
        print(self) // <sample.MyView ...>
        return button
    }()

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        addSubview(button2)
        addSubview(button)
        button.frame = bounds
        print(self) // <sample.MyView ...>
    }

    @objc func taptap() {
        print("taptap")
    }
}

And the printed result is:

(Function)
<sample.MyView: 0x7f961dd09d80; frame = (67 269; 240 128); autoresize = RM+BM; layer = <CALayer: 0x6080000268a0>>
<sample.MyView: 0x7f961dd09d80; frame = (67 269; 240 128); autoresize = RM+BM; layer = <CALayer: 0x6080000268a0>>
taptap

What's the difference self in button closure and self in others? And why my button is works?

Upvotes: 1

Views: 2661

Answers (2)

Sulthan
Sulthan

Reputation: 130102

It works in NSObject subclasses because NSObject (or rather NSObjectProtocol) declares method self. That method is also available on metatypes (which are also NSObject instances) and therefore you can call it in static context.

The fact that it actually works on UIButton is probably a quirk of the compiler and the fact that UIButton accepts Any? as target.

Don't use it, it's not how it's intended to work.

See the bug SR-4559

Upvotes: 4

David Pasztor
David Pasztor

Reputation: 54706

In short, when you declare an instance property using closure initialization, that property will be created before self would be available (before the instance had been initalized properly), hence you cannot access self. A lazy instance property can only ever be accessed after the instance had been initalized, so you can access self from a lazy propery.

Longer version:

If you use closure initializiation by let button: UIButton = { return UIButton() }(), the button variable will be handled the exact same way in runtime as if you simply declared it like let button:UIButton = UIButton(). Pay attention to the () at the end of the closure. That essentially executes the closure right away when the instance property is being initialized, this is why it can actually be declared as immutable. Since instance properties are being initialized before the class initializer would be called, self is not available inside the closure for of a property.

When you declare a variable using the lazy keyword, it will only be evaluated when it is first accessed. Due to this, self is available inside the closure declaring a lazy property, since a property cannot be accessed before the class instance would be created, so in all cases, self is already initialized and available by the time you'd ever access the lazy property.

Upvotes: 2

Related Questions