Retain cycles and how to break them

Memory & ARC Mid-level Swift 5.0+ iOS 17+ Reviewed By Alok Singh

Spot the bug

PlayerScreen never prints 'screen freed'. Tap the line that causes the leak.

  1. protocol PlayerDelegate: AnyObject {
  2. func didFinish()
  3. }
  4. final class Player {
  5. var delegate: PlayerDelegate?
  6. }
  7. final class PlayerScreen: PlayerDelegate {
  8. let player = Player()
  9. init() { player.delegate = self }
  10. func didFinish() {}
  11. deinit { print("screen freed") }
  12. }
Hint 1 of 1

Draw the arrows: who owns whom?

Choose an answer, then open Show answer below to check it.

Show answer
  • Answer: line 5 var delegate: PlayerDelegate?

Line 5 makes the player hold its delegate strongly, while the screen owns the player. Make it weak var delegate: PlayerDelegate?.

30-second answer

A retain cycle happens when two or more objects hold strong references to each other, so their counts never reach zero and they leak. Break it by making one direction weak or unowned, usually the back-reference: delegates, parent pointers and closures that capture their owner.

Practise saying it · 45 s. No recording, just a timer

Explain retain cycles and fixes.

Say your answer out loud, then check which of these points you covered:

  • Mutual strong references
  • weak/unowned back-references
  • Delegates, closures, timers
  • Verify with deinit or the memory graph

Deep dive

Delegates should be weak var delegate: SomeDelegate? with an AnyObject-constrained protocol.

Child-to-parent references are weak; parent-to-child are strong.

Timers, CADisplayLink and NotificationCenter block observers commonly retain their targets.

Verify the fix: deinit should run when the screen closes.

Common mistakes

  • Strong delegate properties.
  • Fixing leaks by setting things to nil in viewWillDisappear.

Senior take & follow-ups

Seniors describe ownership direction (who owns whom) and how they verify with the memory graph.

Follow-up questions

Think of your answer first, then open the hint.

  1. Why must delegate protocols be AnyObject?

    Show hint

    Only class-bound protocols can be weak.

  2. How does Timer create a cycle?

    Show hint

    The run loop retains the timer, and the timer retains its target.

Suggest a fix if something here is wrong or out of date.