Reputation: 689
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
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
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