When I use objc_setAssociatedObject
, I know whether to use retain or assign, but I don't know how to decide between OBJC_ASSOCIATION_RETAIN
and OBJC_ASSOCIATION_RETAIN_NONATOMIC
. When should one or the other be used?
Executive summary: You must use OBJC_ASSOCIATION_RETAIN
if you might call objc_setAssociatedObject
on one thread, and objc_getAssociatedObject
on another thread, simultaneously, with the same object
and key
arguments.
Gory details:
You can look at the implementation of objc_setAssociatedObject
in objc-references.mm
. But actually the difference between OBJC_ASSOCIATION_RETAIN
and OBJC_ASSOCIATION_RETAIN_NONATOMIC
only matters for objc_getAssociatedObject
.
Here are the definitions of those constants in <objc/runtime.h>
:
enum {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
Note that 01401
is 0x0301
and 01403
is 0x0303
. The source code breaks these down further:
enum {
OBJC_ASSOCIATION_SETTER_ASSIGN = 0,
OBJC_ASSOCIATION_SETTER_RETAIN = 1,
OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below.
OBJC_ASSOCIATION_GETTER_READ = (0 << 8),
OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8),
OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8)
};
So we can see that OBJC_ASSOCIATION_RETAIN_NONATOMIC
is just OBJC_ASSOCIATION_SETTER_RETAIN
, but OBJC_ASSOCIATION_RETAIN
is actually OBJC_ASSOCIATION_SETTER_RETAIN | OBJC_ASSOCIATION_GETTER_RETAIN | OBJC_ASSOCIATION_GETTER_AUTORELEASE
.
The OBJC_ASSOCIATION_GETTER_*
bits are examined in _object_get_associative_reference
:
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
}
return value;
}
So if you use OBJC_ASSOCIATION_RETAIN
, it will retain and autorelease the associated object before returning it to you. But there's something else going on that's not obvious. Notice that the function creates a local instance of AssociationsManager
(which is a C++ object stored on the stack). Here's the definition of AssociationsManager
:
class AssociationsManager {
static spinlock_t _lock;
static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
public:
AssociationsManager() { spinlock_lock(&_lock); }
~AssociationsManager() { spinlock_unlock(&_lock); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
So you can see that when the function creates its AssociationsManager
, it acquires a lock, which it holds until the manager is destroyed. The destruction happens after the function has retained the associated object.
The same lock is used when setting a new associated object for the key. This lock prevents a race condition, where one thread is getting the associated object while another is replacing it and causing the object to be deallocated.
If you don't prevent the race condition, then someday your multithreaded app will crash (or worse) by trying to access a deallocated object. You can use OBJC_ASSOCIATION_RETAIN
to prevent the race condition, or you can ensure in some other way that you never set the association on one thread while getting it on another.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With