What is a reference count, and how do you check that you hold the only reference?

3.1.2 · L2 · Interview

Predict first

What does this print?

final class Box {}
var x = Box()
print(isKnownUniquelyReferenced(&x))
let y = x
print(isKnownUniquelyReferenced(&x))
_ = y
  • true, true
  • true, false
  • false, false

Predict first

What does this print?

final class Box {}
var x = Box()
weak var w = x
unowned let u = x
print(isKnownUniquelyReferenced(&x))
_ = (w, u)
  • true
  • false

Mental model. The strong count is the number of people holding the same rope. “Uniquely referenced” asks whether you are the only one pulling.

Each class instance has a small header holding its strong and unowned counts (and a flag for “has a weak side table”). Copying a strong reference adds one to the strong count; dropping one subtracts. At zero the object is deinitialised.

To ask “is this reference the only strong one?” use isKnownUniquelyReferenced(&ref). It is true only when the strong count is exactly one, and weak and unowned references do not count. It is the primitive behind every copy-on-write type. Do not use CFGetRetainCount for logic: temporaries added by the compiler make the number unreliable, and Swift deliberately has no retainCount. Look at counts in the Memory Graph or Instruments instead.

Before y exists the strong count is one. Once y holds the same object the count is two, so it is no longer unique.

Weak and unowned references are not strong references, so they do not change the strong count that this check reads.

Common traps

  • Branching on CFGetRetainCount. The value includes retains you cannot see, so it is an observation tool, never a condition.
  • Expecting weak references to make an object non-unique. They never do.

They might follow up with

  • Why must you pass the variable inout? The check has to read the reference in place, without making a temporary copy that would itself add a count.
  • Where is the count stored? In the object header, next to the type metadata pointer. See the layout section.

Related questions

  • 3.5.1 How do you implement copy-on-write for your own struct?
  • 3.5.2 What exactly does isKnownUniquelyReferenced check, and how do you defeat it by accident?
  • 3.1.1 How does ARC work, and how is it different from garbage collection?

← How does ARC work, and how is it different from garbage collection?   When exactly is an object deallocated: at the end of its scope, or… →