background
In iOS development, if we want to dynamically add attributes to an object or attributes to category, we all implement them through the runtime associated object, how do we access the attributes we add? Is it added directly to the object's own memory? With these questions, let's look at the source code of runtime and untie the mystery of the associated object.
Related object source code
Save value
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { _object_set_associative_reference(object, (void *)key, value, policy); }
When we call this method, we passed four parameters in total:
Parameter name | explain |
---|---|
id object | Objects that need to be associated |
void *key | Corresponding key |
id value | The corresponding value |
objc_AssociationPolicy policy | Memory management policy |
Memory management policy:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { 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. */ };
After understanding the four parameters, let's take a look at its real implementation function_object_set_associative_reference
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { // retain the new value (if any) outside the lock. ObjcAssociation old_association(0, nil); id new_value = value ? acquireValue(value, policy) : nil; { AssociationsManager manager; AssociationsHashMap &associations(()); disguised_ptr_t disguised_object = DISGUISE(object);//Get the object address if (new_value) { // break any existing association. AssociationsHashMap::iterator i = (disguised_object);//First get the hashmap of the object through the address of the object if (i != ()) {//Judge whether it already exists // secondary table exists ObjectAssociationMap *refs = i->second;//Get the value, the corresponding map ObjectAssociationMap::iterator j = refs->find(key);// Find via key if (j != refs->end()) {//If it already exists old_association = j->second;//Get the original old value so that it can be released later j->second = ObjcAssociation(policy, new_value);//Store new value } else {//Not exists (*refs)[key] = ObjcAssociation(policy, new_value); } } else {//If it does not exist, create a // create the new association (first time). ObjectAssociationMap *refs = new ObjectAssociationMap; associations[disguised_object] = refs; (*refs)[key] = ObjcAssociation(policy, new_value); object->setHasAssociatedObjects(); } } else {//Create a // setting the association to nil breaks the association. AssociationsHashMap::iterator i = (disguised_object); if (i != ()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; refs->erase(j); } } } } // release the old value (outside of the lock). if (old_association.hasValue()) ReleaseValue()(old_association); }
Through the above code, we can see that when the associated object is stored, an AssociationsManager singleton object is generated, so all management objects in the application are stored in this AssociationsManager.
The specific storage implementation is implemented with the help of C++'s associated container unordered_map. For details, please refer to the comments I added in the code.
The whole process is to store something similar to a hashmap through the address of the object object; take this hashmap, and then store the value we need to store in this hashmap through key-value pairs. If there is an old value in this process, the old value will be released in the end.
Get the value
The process of obtaining values is actually relatively simple, which is equivalent to taking values from a hashmap
id objc_getAssociatedObject(id object, const void *key) { return _object_get_associative_reference(object, (void *)key); }
id _object_get_associative_reference(id object, void *key) { id value = nil; uintptr_t policy = OBJC_ASSOCIATION_ASSIGN; { AssociationsManager manager; AssociationsHashMap &associations(()); disguised_ptr_t disguised_object = DISGUISE(object); AssociationsHashMap::iterator i = (disguised_object); if (i != ()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { ObjcAssociation &entry = j->second; value = (); policy = (); if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) { objc_retain(value); } } } } if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) { objc_autorelease(value); } return value; }
Summarize
The above is the entire content of this article. I hope that the content of this article has a certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support.