Associated Object Policies

Go deeper than the earlier Associated Objects lesson into the five objc_AssociationPolicy constants -- OBJC_ASSOCIATION_ASSIGN, OBJC_ASSOCIATION_RETAIN_NONATOMIC, OBJC_ASSOCIATION_COPY_NONATOMIC, OBJC_ASSOCIATION_RETAIN, and OBJC_ASSOCIATION_COPY -- covering the precise ownership and thread-safety semantics of each, the canonical category-plus-associated-object pattern for adding a stored property without an ivar, and the dangling-pointer risk of the ASSIGN policy specifically.

The earlier Associated Objects lesson introduced objc_setAssociatedObject and objc_getAssociatedObject as a way to attach extra storage to an object without declaring an ivar for it, using a single, mostly-glossed-over policy argument. This lesson goes back to that policy argument and takes it seriously: it is not a minor detail, it is the exact same kind of ownership decision -assign versus -strong versus -copy properties make at compile time, except made explicitly, by hand, at the point of association -- and getting it wrong produces exactly the kind of memory bugs those property attributes exist to prevent. This lesson covers all five objc_AssociationPolicy constants individually, their atomicity differences, the canonical category use case that associated objects exist for in the first place (categories cannot add ivars, as the sibling Categories lesson covers), and the dangling-pointer mistake that OBJC_ASSOCIATION_ASSIGN specifically invites.

A Deeper Follow-Up to Associated Objects

The earlier Associated Objects lesson covers the mechanics of objc_setAssociatedObject and objc_getAssociatedObject -- attaching a value to an object via a unique key, and reading it back later -- and treats the association policy mostly as a required-but-unexplained third argument, typically shown as OBJC_ASSOCIATION_RETAIN_NONATOMIC without dwelling on why that particular constant or what the alternatives mean.

This lesson goes deeper specifically into that policy argument, because it is doing real, consequential work: exactly like the compiler-checked ownership attributes on an ordinary @property (assign, strong/retain, copy, and their atomic/nonatomic variants), the objc_AssociationPolicy constant determines whether the associated storage retains its value, copies it, or merely holds a raw unmanaged reference to it, and whether reading and writing that association is synchronized against concurrent access from other threads.

There are exactly five constants to cover, each taken in turn below: OBJC_ASSOCIATION_ASSIGN, OBJC_ASSOCIATION_RETAIN_NONATOMIC, OBJC_ASSOCIATION_COPY_NONATOMIC, OBJC_ASSOCIATION_RETAIN, and OBJC_ASSOCIATION_COPY. Together they mirror, almost one-to-one, the property attribute combinations available at compile time -- the difference is that here, there is no compiler enforcing the choice is used consistently or safely.

• This lesson deepens the earlier Associated Objects lesson's treatment of the policy argument, which that lesson covers only briefly. • The policy constant does real ownership work -- retain, copy, or raw-assign -- exactly like a compile-time @property attribute would. • There are exactly five constants: ASSIGN, RETAIN_NONATOMIC, COPY_NONATOMIC, RETAIN, and COPY. • Unlike @property, nothing at compile time checks that the chosen policy is used correctly or consistently.

OBJC_ASSOCIATION_ASSIGN: Unsafe, Unretained-Like Storage

OBJC_ASSOCIATION_ASSIGN stores the associated value as a raw pointer, with no retain, no copy, and no ownership taken whatsoever -- it is the associated-object equivalent of an unsafe_unretained property (or, for non-object values, of storing a plain C scalar or struct directly, which is in fact its other legitimate use, covered in the example below). The associated-object storage does nothing at all to keep the underlying object alive.

This means that if nothing else in the program retains the object that was associated with OBJC_ASSOCIATION_ASSIGN, that object can be deallocated while the association still technically 'holds' a pointer to it -- and reading that association afterward with objc_getAssociatedObject returns a dangling pointer to freed memory, not nil. This is exactly the kind of memory-safety risk this lesson's dedicated mistakes entry below is about, and it is why ASSIGN is by far the riskiest of the five policies for object values specifically.

ASSIGN does have a genuinely safe, common use, though: associating a plain C scalar (an int, a BOOL, a struct) rather than an Objective-C object. Scalars have no retain count to manage in the first place, so ASSIGN's lack of ownership is not a risk there at all -- it is simply the only sensible choice, since RETAIN and COPY are meaningless for a value that isn't an Objective-C object.

• ASSIGN stores a raw pointer with zero ownership -- no retain, no copy -- exactly like an unsafe_unretained property. • If nothing else retains the associated object, it can be deallocated while the association still points at it, producing a dangling pointer on the next read. • objc_getAssociatedObject after the object is gone returns garbage/a dangling pointer, not nil -- there is no automatic nil-ing out. • ASSIGN is genuinely safe (and the only sensible choice) for plain C scalars and structs, which have no retain count to manage.

OBJC_ASSOCIATION_RETAIN_NONATOMIC and OBJC_ASSOCIATION_COPY_NONATOMIC

OBJC_ASSOCIATION_RETAIN_NONATOMIC retains the associated object -- calling retain on it when associated, and release when the association is later overwritten or the owning object is deallocated -- exactly like a strong, nonatomic property. This is overwhelmingly the most commonly used policy in real code, because it gives ordinary strong-reference lifetime semantics: the associated object stays alive for as long as the association exists, with no dangling-pointer risk the way ASSIGN carries.

OBJC_ASSOCIATION_COPY_NONATOMIC instead copies the value being associated -- invoking -copy on it -- exactly like a copy, nonatomic property. This is the correct choice whenever the associated value is a naturally-copyable, value-like type where you want the association to hold an independent snapshot rather than a shared reference to a mutable original -- most commonly NSString (guarding against someone else mutating an NSMutableString out from under the association after the fact) or a block (where -copy is additionally what moves a stack-allocated block to the heap, exactly as covered in the sibling Blocks-related lessons).

The 'NONATOMIC' half of both these names means exactly what it means for an ordinary property: reading and writing the association is not synchronized against concurrent access from multiple threads. For the overwhelming majority of associated-object use, on objects that are themselves only ever touched from one thread (or externally synchronized some other way), this is the correct and sufficient choice, and it is measurably cheaper than the atomic variants covered next.

• RETAIN_NONATOMIC: retains the associated object, exactly like a strong, nonatomic property -- the most commonly used policy in practice. • COPY_NONATOMIC: copies the associated value via -copy, exactly like a copy, nonatomic property -- correct for NSString-like value types and blocks. • -copy on a block additionally moves it from the stack to the heap, mirroring ordinary block-copying semantics covered elsewhere in this course. • 'NONATOMIC' means the get/set access to the association itself is not synchronized -- fine for single-threaded or externally-synchronized use, and cheaper than the atomic variants.

OBJC_ASSOCIATION_RETAIN and OBJC_ASSOCIATION_COPY: The Atomic Variants

OBJC_ASSOCIATION_RETAIN and OBJC_ASSOCIATION_COPY are the atomic counterparts of the two nonatomic policies just covered -- identical retain-or-copy ownership semantics, but with the actual get/set operation on the association itself synchronized, exactly the same relationship an atomic @property has to its nonatomic counterpart.

Concretely, this means that if one thread calls objc_setAssociatedObject with a new value for a given key while another thread concurrently calls objc_getAssociatedObject for that same key, the atomic policies guarantee the read either sees the fully-old value or the fully-new value -- never a torn, half-written intermediate state. This does not, importantly, make arbitrary compound operations on the associated value thread-safe (reading it, mutating it, and writing it back is still a race regardless of policy) -- it only protects the single get or set call itself, exactly the same narrow guarantee an atomic property attribute provides and no more.

In practice, the atomic variants are used far less often than their nonatomic counterparts, for the same reason atomic properties in general are the less common choice in modern Objective-C code: most objects, and most associated values on them, are only ever touched from a single well-defined thread (often the main thread, or a single serial dispatch queue), making the extra synchronization overhead of the atomic policies pure cost with no benefit.

• RETAIN and COPY are the atomic counterparts of RETAIN_NONATOMIC and COPY_NONATOMIC -- same ownership, but synchronized get/set. • The guarantee is narrow: a concurrent get/set pair sees either the fully-old or fully-new value, never a torn intermediate state. • This does NOT make read-modify-write sequences on the value thread-safe -- only the single get or set call itself is protected. • The atomic variants are used far less often in practice, for the same reasons atomic properties generally are -- most associated values are single-thread-only in real code.

Categories Cannot Add Ivars: The Canonical Associated-Object Use Case

The single most common real-world reason associated objects exist at all is a limitation covered in full in the sibling Categories lesson: a category can add new methods to an existing class, even one you don't own the source for, but it fundamentally cannot add new instance variables -- the class's memory layout is already fixed by the time any category runs, exactly the same structural reason covered in the sibling Runtime Class Creation lesson for why class_addIvar fails on an already-registered class.

Associated objects are the standard workaround: a category can define what looks and behaves like a full property -- a getter, a setter, and persistent per-instance storage -- entirely through objc_setAssociatedObject and objc_getAssociatedObject, using the category's own method's address (or a dedicated static char variable's address) as a guaranteed-unique key, with no ivar declared anywhere.

This pattern is genuinely everywhere in real Objective-C code: adding a completion-handler block property to a UIView via a category, attaching arbitrary metadata to model objects from a third-party library without subclassing, or, most visibly, how many popular open-source categories on UIKit/Foundation classes add what look like ordinary stored properties to classes the app doesn't own the source of at all.

• Categories can add methods but fundamentally cannot add ivars -- the class's memory layout is already fixed, same structural reason as class_addIvar failing post-registration. • Associated objects let a category simulate a full property (getter, setter, persistent per-instance storage) with zero ivars. • The key used with objc_setAssociatedObject / objc_getAssociatedObject just needs to be a guaranteed-unique, stable address -- commonly a static char variable's address. • This is the single most common real-world reason to reach for associated objects at all: adding property-like storage via a category on a class you may not even own the source for.

Thread-Safety Considerations Across All Five Policies

It is worth being precise about exactly what 'atomic' buys you across these five policies, because it is easy to over-trust it. The atomic policies (RETAIN and COPY) protect only the association's own internal get/set operation -- objc_getAssociatedObject and objc_setAssociatedObject themselves are guaranteed not to race against each other destructively for the same object-and-key pair. They say nothing whatsoever about the thread-safety of the underlying object being associated, and nothing about compound operations built out of multiple association calls.

For OBJC_ASSOCIATION_ASSIGN specifically, thread safety is at its weakest: because there is no ownership at all, one thread can deallocate the associated object (by releasing its last strong reference elsewhere in the program) at the exact same moment another thread reads the association and dereferences what is now a dangling pointer -- no association policy of any kind can protect against a use-after-free that originates entirely outside the association mechanism itself.

The practical guidance is straightforward: use the nonatomic variants (by far the common case) when the associated object and its owner are only ever touched from a single thread or queue, reach for the atomic variants only when genuine cross-thread get/set access is expected and you specifically need to avoid a torn read, and treat OBJC_ASSOCIATION_ASSIGN as requiring the same external lifetime discipline an unsafe_unretained property would need -- which, in most real code, means avoiding it for object values entirely in favor of RETAIN_NONATOMIC.

• Atomic policies protect only the single get/set call on the association itself -- not the underlying object's thread safety, and not any compound operation. • ASSIGN has the weakest thread-safety story of all five: nothing prevents another thread from deallocating the associated object out from under a concurrent read. • No association policy protects against a use-after-free whose root cause is entirely outside the association mechanism. • Practical rule of thumb: nonatomic for single-thread/queue use (the common case), atomic only for genuine cross-thread get/set, and avoid ASSIGN for object values.

Memory-Management Risks Beyond the Dangling Pointer

Beyond the ASSIGN dangling-pointer risk covered in this lesson's dedicated mistakes entry, associated objects carry a subtler, easy-to-miss risk with the retaining policies: because the association itself holds a strong reference to whatever was set, a retain cycle is entirely possible if the associated value (directly or indirectly, through a captured block) ends up strongly referencing the very object it's associated with. A completion-handler block associated onto a view, for instance, that captures self (that same view) strongly, creates exactly this kind of cycle -- the view retains the block via the association, and the block retains the view right back.

The fix mirrors the ordinary block-retain-cycle fix covered elsewhere in this course: capture the owning object weakly inside any block that will be stored as an associated value on that same object, exactly as you would for a block stored in an ordinary strong property.

Finally, because associated-object storage lives in a runtime-managed side table keyed by object identity rather than as part of the object's own ivar layout, removing an association (by calling objc_setAssociatedObject again with nil for that key) is the only way to release it before the owning object itself is deallocated -- there is no way to 'reach into' an object's associations generically to enumerate or clear all of them at once, so any object that sets associated values it wants to control the lifetime of must track and clear its own keys deliberately.

• A retaining association can form a retain cycle if the associated value strongly captures the very object it's attached to (common with blocks). • The fix is the same as any block-retain-cycle fix: capture the owning object weakly inside a block that will be stored as an associated value. • Associated storage lives in a side table, not the object's own ivars -- setting a key to nil is the only way to release it early. • There is no generic way to enumerate or clear all of an object's associations at once; code that needs to must track its own keys.

Code Examples

OBJC_ASSOCIATION_ASSIGN stores the raw pointer with zero ownership, exactly like an unsafe_unretained property. It is only safe here because someDelegate's actual lifetime is guaranteed by a strong reference held somewhere else entirely -- the association itself provides no such guarantee at all.

objc_setAssociatedObject(self, kAssociatedDelegateKey, someDelegate, OBJC_ASSOCIATION_ASSIGN);

RETAIN_NONATOMIC is the workhorse policy: the association retains model when set and releases it when overwritten or when self is deallocated, giving ordinary strong-reference lifetime semantics with no synchronization overhead -- correct for the overwhelming majority of single-thread associated-object use.

objc_setAssociatedObject(self, kAssociatedModelKey, model, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

COPY_NONATOMIC takes an independent snapshot via -copy rather than sharing the original reference, which is exactly why later mutating mutableTitle has no effect on what's stored -- correct whenever the associated value is a naturally value-like type. On a block specifically, -copy also performs the ordinary stack-to-heap promotion blocks need to safely outlive the scope that created them.

objc_setAssociatedObject(self, kAssociatedTitleKey, mutableTitle, OBJC_ASSOCIATION_COPY_NONATOMIC);

RETAIN and COPY provide the identical ownership behavior as their nonatomic counterparts, with the difference confined entirely to synchronizing each individual get or set call against concurrent access. The commented read-modify-write sequence demonstrates the narrow scope of that guarantee: the two calls together are still not protected as a unit, exactly the same limitation atomic properties have.

objc_setAssociatedObject(self, kAssociatedCounterKey, counterObject, OBJC_ASSOCIATION_RETAIN);

UIView is a framework class this category doesn't own the source for, so it cannot add an ivar -- the exact limitation the sibling Categories lesson covers. Associated objects, with the COPY_NONATOMIC policy matching what the equivalent compiled @property (nonatomic, copy) attribute would produce, let the category present a fully working, ordinary-looking stored property with no ivar declared anywhere.

objc_setAssociatedObject(self, kTapCompletionHandlerKey, tapCompletionHandler, OBJC_ASSOCIATION_COPY_NONATOMIC);

static const void *kAssociatedDelegateKey = &kAssociatedDelegateKey;

// ASSIGN: no retain, no copy -- the association does nothing to keep
// 'someDelegate' alive. Safe here ONLY because 'someDelegate' is also
// strongly retained elsewhere (e.g. a property on its own owner).
objc_setAssociatedObject(self, kAssociatedDelegateKey, someDelegate,
                          OBJC_ASSOCIATION_ASSIGN);

id readBack = objc_getAssociatedObject(self, kAssociatedDelegateKey);
// If 'someDelegate' has since been deallocated elsewhere, 'readBack'
// is now a DANGLING pointer, not nil -- see the mistakes entry below.
static const void *kAssociatedModelKey = &kAssociatedModelKey;

// RETAIN_NONATOMIC: retains 'model' -- exactly like a strong, nonatomic
// property. This is the default, safe, most commonly used policy.
objc_setAssociatedObject(self, kAssociatedModelKey, model,
                          OBJC_ASSOCIATION_RETAIN_NONATOMIC);

id storedModel = objc_getAssociatedObject(self, kAssociatedModelKey);
// 'storedModel' is guaranteed alive as long as the association exists --
// no dangling-pointer risk the way ASSIGN carries.
static const void *kAssociatedTitleKey = &kAssociatedTitleKey;
static const void *kAssociatedHandlerKey = &kAssociatedHandlerKey;

NSMutableString *mutableTitle = [NSMutableString stringWithString:@"Draft"];

// COPY_NONATOMIC: copies 'mutableTitle' at the moment of association --
// later mutating 'mutableTitle' elsewhere does NOT affect the stored copy.
objc_setAssociatedObject(self, kAssociatedTitleKey, mutableTitle,
                          OBJC_ASSOCIATION_COPY_NONATOMIC);
[mutableTitle appendString:@" (unsaved)"];   // does not affect the association

// COPY_NONATOMIC on a block also moves a stack block to the heap.
void (^handler)(void) = ^{ NSLog(@"handled"); };
objc_setAssociatedObject(self, kAssociatedHandlerKey, handler,
                          OBJC_ASSOCIATION_COPY_NONATOMIC);
static const void *kAssociatedCounterKey = &kAssociatedCounterKey;
static const void *kAssociatedLabelKey = &kAssociatedLabelKey;

// RETAIN: same retain semantics as RETAIN_NONATOMIC, but the get/set
// itself is synchronized against concurrent access from other threads.
objc_setAssociatedObject(self, kAssociatedCounterKey, counterObject,
                          OBJC_ASSOCIATION_RETAIN);

// COPY: same copy semantics as COPY_NONATOMIC, atomically synchronized.
objc_setAssociatedObject(self, kAssociatedLabelKey, someLabelString,
                          OBJC_ASSOCIATION_COPY);

// Even with the atomic policies, a read-modify-write sequence like this
// is STILL a race across two threads -- atomicity only covers each
// individual get or set call, not the pair together:
id current = objc_getAssociatedObject(self, kAssociatedCounterKey);
// ... another thread could set a new value for kAssociatedCounterKey here ...
objc_setAssociatedObject(self, kAssociatedCounterKey, updatedCounterObject,
                          OBJC_ASSOCIATION_RETAIN);
// UIView+CompletionHandler.h
@interface UIView (CompletionHandler)
@property (nonatomic, copy) void (^tapCompletionHandler)(void);
@end

// UIView+CompletionHandler.m
static const void *kTapCompletionHandlerKey = &kTapCompletionHandlerKey;

@implementation UIView (CompletionHandler)

- (void)setTapCompletionHandler:(void (^)(void))tapCompletionHandler {
    // COPY_NONATOMIC: correct for a block-typed "property", matching
    // exactly what @property (nonatomic, copy) would do if this were
    // a real, compiled property with a real ivar.
    objc_setAssociatedObject(self, kTapCompletionHandlerKey,
                              tapCompletionHandler,
                              OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (void (^)(void))tapCompletionHandler {
    return objc_getAssociatedObject(self, kTapCompletionHandlerKey);
}

@end

The Five Association Policies and Their Property-Attribute Equivalents

Each association policy mirrors a compile-time @property attribute combination almost exactly -- the difference is that nothing at compile time checks the choice is used correctly, which is exactly why the ASSIGN policy's dangling-pointer risk (covered in this lesson's mistakes entry) is a real, common bug rather than a compiler error.

OBJC_ASSOCIATION_ASSIGN              ~ unsafe_unretained  (no ownership)
OBJC_ASSOCIATION_RETAIN_NONATOMIC    ~ strong, nonatomic   (retains)
OBJC_ASSOCIATION_COPY_NONATOMIC      ~ copy,   nonatomic   (copies)
OBJC_ASSOCIATION_RETAIN              ~ strong, atomic      (retains, synced)
OBJC_ASSOCIATION_COPY                ~ copy,   atomic      (copies, synced)

Where Policy Choice Actually Matters

In practice, the overwhelming majority of associated-object code should default to OBJC_ASSOCIATION_RETAIN_NONATOMIC for object values and reach for OBJC_ASSOCIATION_COPY_NONATOMIC specifically for blocks and NSString-like value types, mirroring the same defaults ordinary @property declarations use for the same kinds of values. OBJC_ASSOCIATION_ASSIGN should be treated as reserved for plain C scalars and structs, or for the rare case of an object whose lifetime is deliberately and verifiably managed elsewhere; using it as a shortcut for an object without that guarantee is one of the more common sources of hard-to-reproduce, timing-dependent crashes in codebases that lean on associated objects heavily, precisely because the resulting dangling-pointer read often happens long after, and far away from, the code that actually deallocated the object.

objc_setAssociatedObject(self, kKey, temporaryObject, OBJC_ASSOCIATION_ASSIGN); // 'temporaryObject' has no other strong reference anywhere in the program.

objc_setAssociatedObject(self, kKey, temporaryObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // The association itself now keeps 'temporaryObject' alive.

Using OBJC_ASSOCIATION_ASSIGN on an object with no other strong owner leaves the association holding a raw, unowned pointer. As soon as that object's last strong reference elsewhere goes away, it is deallocated -- but the association still 'holds' its address, so the next objc_getAssociatedObject call returns a dangling pointer to freed memory, not nil, and sending any message to it is undefined behavior. Unless a scalar/struct is being stored, or some other part of the program is guaranteed to keep the object alive for the association's entire lifetime, RETAIN_NONATOMIC (or RETAIN, if cross-thread access is genuinely needed) is almost always the correct policy for object values.

What ownership behavior does OBJC_ASSOCIATION_ASSIGN provide for an associated object value? A. It retains the value, exactly like OBJC_ASSOCIATION_RETAIN_NONATOMIC B. It copies the value via -copy before storing it C. None -- it stores a raw, unowned pointer, exactly like an unsafe_unretained property D. It automatically converts the value to a weak reference that nils itself out on deallocation

C. None -- it stores a raw, unowned pointer, exactly like an unsafe_unretained property — OBJC_ASSOCIATION_ASSIGN performs no retain and no copy -- it stores a raw pointer with zero ownership. Unlike a weak property, it does not automatically nil itself out when the referenced object is deallocated, which is exactly the source of the dangling-pointer risk this lesson covers.

What is the practical difference between OBJC_ASSOCIATION_RETAIN_NONATOMIC and OBJC_ASSOCIATION_RETAIN? A. RETAIN copies the value while RETAIN_NONATOMIC only retains it B. They have identical ownership (retain) semantics; RETAIN additionally synchronizes the association's own get/set calls against concurrent access, while RETAIN_NONATOMIC does not C. RETAIN_NONATOMIC can only be used on the main thread; RETAIN can be used on any thread D. There is no difference -- the two names are aliases for the same constant

B. They have identical ownership (retain) semantics; RETAIN additionally synchronizes the association's own get/set calls against concurrent access, while RETAIN_NONATOMIC does not — Both retain the associated value identically. The only difference is atomicity: RETAIN synchronizes the individual get/set operation on the association against concurrent access from other threads, while RETAIN_NONATOMIC does not -- mirroring the atomic/nonatomic distinction on an ordinary @property exactly.

Why do categories rely on associated objects as the standard way to add what looks like a stored property? A. Because associated objects are faster than ordinary ivars B. Because a category fundamentally cannot add a new instance variable to a class -- its memory layout is already fixed -- so associated objects provide storage through a side table instead C. Because @property cannot be declared inside a category for any reason D. Because Apple deprecated ivars entirely in favor of associated objects

B. Because a category fundamentally cannot add a new instance variable to a class -- its memory layout is already fixed -- so associated objects provide storage through a side table instead — A category can add methods but cannot add ivars, because the class's memory layout is already fixed by the time any category runs -- the same structural limitation covered for class_addIvar on already-registered classes in the Runtime Class Creation lesson. Associated objects provide per-instance storage through a side table instead, letting a category simulate a full stored property.

A block stored as an associated value should typically use which policy, and why? A. OBJC_ASSOCIATION_ASSIGN, because blocks manage their own memory independently B. OBJC_ASSOCIATION_COPY_NONATOMIC (or COPY), because -copy is what promotes a block from the stack to the heap so it safely outlives its creating scope, matching what a copy property would do C. OBJC_ASSOCIATION_RETAIN_NONATOMIC, because retaining a stack block is always sufficient D. None of the five policies support storing blocks as associated values

B. OBJC_ASSOCIATION_COPY_NONATOMIC (or COPY), because -copy is what promotes a block from the stack to the heap so it safely outlives its creating scope, matching what a copy property would do — A block created in ordinary code often starts on the stack; -copy (invoked by the COPY policies) promotes it to the heap so it remains valid after the creating scope exits -- the same reason ordinary block-typed properties are conventionally declared copy rather than strong.

What specifically goes wrong when OBJC_ASSOCIATION_ASSIGN is used on an object that has no other strong owner in the program? A. objc_setAssociatedObject throws an exception immediately B. The object is deallocated once its last other strong reference goes away, and a later objc_getAssociatedObject call returns a dangling pointer to freed memory rather than nil C. The compiler rejects the code at build time D. The associated value is automatically upgraded to OBJC_ASSOCIATION_RETAIN_NONATOMIC at runtime

B. The object is deallocated once its last other strong reference goes away, and a later objc_getAssociatedObject call returns a dangling pointer to freed memory rather than nil — Because ASSIGN provides no ownership, the object's lifetime depends entirely on strong references held elsewhere. If none remain, it is deallocated while the association still 'points' at it, and reading the association afterward yields a dangling pointer -- not nil -- since there is no automatic nil-ing-out the way a weak reference provides.

What exactly does the 'atomic' guarantee of OBJC_ASSOCIATION_RETAIN and OBJC_ASSOCIATION_COPY protect against? A. It guarantees a read-modify-write sequence spanning multiple objc_getAssociatedObject/objc_setAssociatedObject calls is race-free B. It guarantees the underlying associated object itself is thread-safe to use from multiple threads C. It guarantees only that a single get or set call sees a fully-old or fully-new value, never a torn intermediate state -- nothing more D. It prevents the associated object from ever being deallocated while any thread holds a reference to it

C. It guarantees only that a single get or set call sees a fully-old or fully-new value, never a torn intermediate state -- nothing more — The atomic policies synchronize only each individual get or set call on the association. They do not make compound operations (read, modify, write) safe across threads, and they say nothing about the thread-safety of the associated object's own internal state -- exactly the same narrow scope an atomic @property provides.

A category on NSObject wants to add a 'lastAccessedDate' stored property (an NSDate, replaced wholesale on each access, never mutated in place) that is only ever read and written from the main thread. Write the getter and setter using associated objects with the most appropriate of the five policies, and explain in one sentence why the atomic variant would be unnecessary overhead here. Then explain why OBJC_ASSOCIATION_ASSIGN would be the wrong choice for this specific property.

• (void)setLastAccessedDate:(NSDate *)date { objc_setAssociatedObject(self, kLastAccessedDateKey, date, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }\n- (NSDate *)lastAccessedDate { return objc_getAssociatedObject(self, kLastAccessedDateKey); }\n\nRETAIN_NONATOMIC (not COPY_NONATOMIC) is appropriate because NSDate instances are replaced wholesale rather than mutated in place, so there's no risk of an external mutable reference changing the stored value out from under the association the way NSMutableString would carry -- a plain retain is sufficient. The atomic variant would be unnecessary overhead because the property is stated to be accessed only from the main thread, so there is no concurrent get/set to protect against. OBJC_ASSOCIATION_ASSIGN would be wrong because nothing else in this scenario is stated to keep the NSDate alive independently -- without the association's own retain, the date could be deallocated while still 'associated', producing a dangling pointer on the next read.

NSDate instances are typically treated as immutable value-like objects that get replaced wholesale rather than mutated in place, and the property is stated to be main-thread-only.