I was looking into ARC and strong reference cycles and ran into this code of mine:
class TestClass: UIView {
let button: UIButton = {
let view = UIButton()
view.frame = CGRect(x: 50, y: 50, width: 200, height: 200)
view.backgroundColor = .blue
view.translatesAutoresizingMaskIntoConstraints = false
view.setTitle("Button", for: .normal)
view.addTarget(self, action: #selector(buttonClicked), for: .touchUpInside)
return view
}()
@objc private func buttonClicked() {
print("Clicked")
}
override init(frame: CGRect) {
super.init(frame: frame)
print("Object of TestClass initialized")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("Object of TestClass deinitialized")
}
}
reference to self
in the addTarget
method inside the closure doesn't seem to create a strong reference cycle.
Can someone explain why?
Also, I noticed that if I remove inheritance from UIView
the compiler starts complaining: Use of unresolved identifier 'self'
.
Can someone explain this as well, why does it happen in this case and doesn't in the first one?
This is not a retain cycle because self
is not what you think it is :)
Properties with initial value are "executed" even before any initializer runs, and for those properties self
points to a higher order function of this type:
(TestClass) -> () -> TestClass
So you don't really access the instance, but rather you access a static
-like method that does the initialization of all properties that have a default value. This is why you don't have a retain cycle.
addTarget
accepts an Any?
value for it's first argument, so this violates no type rules so the compiler doesn't complain that you don't pass a NSObject
instance there.
Checking the later behaviour - e.g. what happens if the button is added to the UI hierarchy and is tapped, reveals something interesting: the runtime sees that you passed a non-object as a target and sets null values for target and action: