Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

objc_setAssociatedObject retain atomic or nonatomic

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?

like image 512
Jeff Avatar asked Apr 29 '15 04:04

Jeff


1 Answers

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.

like image 100
rob mayoff Avatar answered Oct 17 '22 01:10

rob mayoff