I was reading retain cycle and tried the following code which should in theory leak memory but when I use Instruments, it does not show any memory leaks
import UIKit
class ViewController: UIViewController {
private var button = {
let _button = UIButton()
_button.setTitle("Tap Me", for: .normal)
_button.setTitleColor(.systemBlue, for: .normal)
return _button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(button)
button.addTarget(self, action: #selector(actionButton), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
button.widthAnchor.constraint(equalToConstant: 80),
button.heightAnchor.constraint(equalToConstant: 30)
])
}
@objc private func actionButton() {
let secondVC = ViewController2()
present(secondVC, animated: true)
}
}
class MyView: UIView {
let viewContoller: ViewController2
init(from vc: ViewController2) {
viewContoller = vc
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ViewController2: UIViewController {
var myView: MyView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
myView = MyView(from: self)
}
}
There’s a button on ViewController
which upon tapping opens a new view controller ViewController2
which initialises a UIView
and when I dismiss that view controller(ViewController2), it should create a retain cycle (since the view also holds ViewController2 object). Where exactly am I going wrong?
Edit 1: I tried the deinit
inside the ViewController2
and it worked as expected. However, the confusion started when I tried to profile the same code on Instruments app and it showed "No Leaks", green ticks all the way
2
Answers
You have successfully created a retain cycle. Your only problem is how to detect it. To detect the retain cycle, add these lines to MyView and ViewController2, respectively:
Now run your app, tap the button, and dismiss (by dragging) the presented view controller. Nothing prints to the console. Thus we know that neither ViewController2 nor MyView are being deallocated. That’s the leak.
It will also help to make a contrasting situation so that you can see that the "farewell" messages would print if there were not a retain cycle. To make it, change MyView like this:
Now change
to
Run the app, tap the button, and dismiss (by dragging) the presented view controller. The
deinit
messages print! Thus we have created a red-green test that demonstrates the retain cycle. You can change the code back toand confirm that the
deinit
message do not print. Have fun playing with your brand new retain cycle demonstration app!You are in fact creating a retain cycle, you can either check the debug memory graph after dismissing
ViewController2
and the instance will be there: 1Or you can implement the
deinit
method and log something:In any case, with the code you provided the instance will be showing in the memory graph and the
deinit
method won’t be called in either the view nor the controller 2.In order to break the cycle you would need a reference to be weak (or unowned) like so: