What are associated objects, and when are they the right (or wrong) tool?

4.2.2 · L3 · Interview

Predict first

What does this print?

var key: UInt8 = 0
final class Owner: NSObject {}
final class Payload { deinit { print("payload gone") } }

do {
    let o = Owner()
    objc_setAssociatedObject(o, &key, Payload(), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    print(objc_getAssociatedObject(o, &key) != nil)
}
print("after scope")
  • true, after scope, payload gone
  • true, payload gone, after scope
  • true, after scope

Predict first

What does this print?

var key: UInt8 = 0
let o = NSObject()
objc_setAssociatedObject(o, &key, "v", .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
print(objc_getAssociatedObject(o, &key) as? String as Any)
  • Optional("v")
  • nil

Mental model. Associated objects are luggage tags that carry a note on an object you were not allowed to open. The note travels with it and is thrown away when it is.

Since a category cannot add a stored property, the runtime offers associated objects: objc_setAssociatedObject(obj, key, value, policy) and objc_getAssociatedObject(obj, key) attach a value to any object, looked up by the address of a key. The policy says how it is held (assign, retain, copy, atomic or not). The value is released when the owner deallocates, so the lifetime follows the object without you doing anything.

It is a pragmatic tool, not a clean one. Nothing is typed (you cast on the way out), the key must be a unique pointer (a static variable’s address), and the storage is invisible to the class’s authors, which hurts debugging. Use it to add a small piece of state from a category or to hang a helper object on a system class. Prefer a subclass, a wrapper or a real property when you own the type, and never use assign for an object you do not keep alive elsewhere, which leaves a dangling pointer.

The owner holds the payload. When the owner leaves the do scope and deallocates, the runtime releases the payload, so it dies before after scope.

The value comes back as Any?, so you cast it. The key is identified by its address, not its contents.

Common traps

  • Using an NSString literal as a key. Equal-looking literals may or may not share an address. Use a dedicated key variable.
  • Reaching for it on your own types. A plain stored property is typed, discoverable and faster.

They might follow up with

  • Is it thread-safe? The atomic policies make each get and set safe, but not read-modify-write sequences.
  • What does OBJC_ASSOCIATION_RETAIN do on release? The owner’s deallocation releases the associated value for you.

Related questions

  • 4.2.1 What is an Objective-C category, and how does it differ from a Swift extension?
  • 4.6.1 How does method swizzling work, and why does it need dynamic in Swift?

← What is an Objective-C category, and how does it differ from a Swift…   What are +load and +initialize, and which can Swift use? →