5

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?

Sasha Kolsky
  • 432
  • 1
  • 5
  • 14

1 Answers1

3

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:

enter image description here

Cristik
  • 30,989
  • 25
  • 91
  • 127
  • Thanks Cristik, it'll take me some time to digest the first part of your answer, but in regard with the second part. The `print("Clicked")` get's executed. Should that not be the case, since like you said, both target and action are set to null? – Sasha Kolsky Mar 08 '18 at 23:32
  • @Alexander.Kazakov from the code you posted that should not be possible, for the simple reason you are not adding the button in the UI hierarchy – Cristik Mar 09 '18 at 06:22
  • Assume the button is added to the ui hierarchy. Why does it work? – Sasha Kolsky Mar 16 '18 at 23:58
  • @Alexander.Kazakov using the code from the question and adding the button in the UI hierarchy results in the behaviour described in the answer. Perhaps you're doing something different in your app. – Cristik Mar 17 '18 at 04:23