Introduced
The Association object (associatedobject) is an attribute of the OBJECTIVE-C 2.0 runtime, allowing the developer to add custom attributes to an existing class in the extension. In the actual production process, the more common way is to add member variables to the category (category).
Example
#import <objc/runtime.h>@interface NSObject (AssociatedObject)@property (nonatomic, strong) id property;@end@implementation NSObject (AssociatedObject)@dynamic property;- (id)property { return objc_getAssociatedObject(self, _cmd);}- (void)setProperty:(NSString *)property { objc_setAssociatedObject(self, @selector(property), property, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}@end
By implementing the code can be slightly analyzed, holding the constant objc_getAssociatedObject
pointer address (example passed in selector as a parameter, actually void*), from the instance to get the desired object. objc_setAssociatedObject
saves the specified object according to the parameter protocol passed in.
Parameter protocol
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. */};
In fact, these five protocols are what we usually use when defining attributes, and it is important to note that although Apple says it is equivalent to one in the comments, it OBJC_ASSOCIATION_ASSIGN
weak reference
actually equals assign/unsafe_unretained
.
For weak
the difference is not within the scope of this article, the obvious difference is that after the variable is released, the reference will be weak
empty, the unsafe_unretained
memory address will be preserved, once the gain may be the wild pointer flash back.
Summarize
We know that if a class is to add a variable, only objc_allocateClassPair
objc_registerClassPair
between and addIvar
. Once the class is registered, the variable structure is not allowed to be changed, in order to prevent two instances of the same class having different variables resulting in running confusion.
It is a good practice to add variables to the instance at runtime without changing the structure of the internal variables of the class.
Implementing an external method for an associated object
//Sets an associated value for a given object using a given key and association policy.void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy);//Returns the value associated with a given object for a given key.id objc_getAssociatedObject(id object, const void * key);//Removes all associations for a given object.void objc_removeAssociatedObjects(id object);
Compared to the usage in the example, one more objc_removeAssociatedObjects
, can you use this method to delete the unused associated objects?
Apple's documentation explains that this method is primarily used to restore objects to the initial state of the class, removing all associations, including additions to other modules, and therefore should be objc_setAssociatedObject(..,nil,..)
uninstalled in the same way.
Setter implementation
objc_setAssociatedObject
The actual call is_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 (Manager.associations ()); disguised_ptr_t Disguised_object = Disguise (object); if (new_value) {//Break any existing association. Associationshashmap::iterator i = Associations.find (disguised_object); if (i! = Associations.end ()) {//secondary table exists objectassociationmap *refs = i-> Second Objectassociationmap::iterator j = refs->find (key); if (J! = Refs->end ()) {old_association = j->second; J->second = objcassociation (policy, new_value); } else {(*refs) [KeY] = objcassociation (policy, new_value); }} else {//Create the new association (first time). Objectassociationmap *refs = new Objectassociationmap; Associations[disguised_object] = refs; (*refs) [Key] = objcassociation (policy, new_value); Object->sethasassociatedobjects (); }} else {//Setting the association to nil breaks the association. 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 ()) {old_association = j->second; Refs->erase (j); }}}}//Release the old value (outside of the lock). if (Old_association.hasvalue ()) ReleasevAlue () (old_association);}
Memory management
static id acquireValue(id value, uintptr_t policy) { switch (policy & 0xFF) { case OBJC_ASSOCIATION_SETTER_RETAIN: return objc_retain(value); case OBJC_ASSOCIATION_SETTER_COPY: return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy); } return value;}static void releaseValue(id value, uintptr_t policy) { if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) { return objc_release(value); }}ObjcAssociation old_association(0, nil);id new_value = value ? acquireValue(value, policy) : nil;{ old_association = ...}if (old_association.hasValue()) ReleaseValue()(old_association);
We pick out the code that is related to the object memory carefully, first put the new incoming object, according to the protocol retain/copy
, in the process of assigning the value of the old value, before the method ends release
.
Assign value
Associationsmanager Manager; Associationshashmap &associations (Manager.associations ());d isguised_ptr_t Disguised_object = DISGUISE (object); if (new_value) {//requires assignment associationshashmap::iterator i = Associations.find (disguised_object); if (i! = Associations.end ()) {//found the associated table for this object objectassociationmap *refs = i->second; Objectassociationmap::iterator j = refs->find (key); if (J! = Refs->end ()) {//found the associated object of this key old_association = j->second; J->second = objcassociation (policy, new_value); } else {//not found, add an association (*REFS) [Key] = objcassociation (policy, new_value); }} else {//not found, create a new association table Objectassociationmap *refs = new Objectassociationmap; Associations[disguised_object] = refs; (*refs) [Key] = objcassociation (policy, new_value); Object->sethasassociatedobjects (); }}
First Look at AssociationsManager
theAssociationsHashMap
class AssociationsManager { static AssociationsHashMap *_map;public: AssociationsHashMap &associations() { if (_map == NULL) _map = new AssociationsHashMap(); return *_map; }};class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator>;class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator>;
AssociationsManager
Manages all the associated objects within an application through a hash table with a pointer address as the primary key and a value of the associated tables.
First, the object's pointer address to find the association table, and then through the specified key value to find the association, thereby obtaining the associated object.
Delete
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()) { old_association = j->second; refs->erase(j); }}
Similar to the modification method, the method of executing the hash table is removed after the association relationship is found erase
.
Getter implementation
objc_getAssociatedObject
The actual call is_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) {Objc_retain (value); }}}} if (Value && policy & objc_association_getter_autorelease) {Objc_aut Orelease (value); } return value;
The way to find a hash table is the same as a setter, except that if you need retain and autorelease in your strategy, you need to deal with it. So how do we agree to these strategies?
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)}; typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403};
OBJC_ASSOCIATION_RETAIN = 01401
, where the 01401
beginning is 0
, so is the octal number, translated into binary is 0000 0011 0000 0001
, take a bit to judge is OBJC_ASSOCIATION_SETTER_RETAIN
OBJC_ASSOCIATION_GETTER_RETAIN
OBJC_ASSOCIATION_GETTER_AUTORELEASE
.
When you save, you need to retain
increase the reference count first, and retain
then autorelease
wait for the release to achieve atomicity when you get it.
Remove implementation
objc_removeAssociatedObjects
Determines whether an object has an association, and then executes the_object_set_associative_reference
void _object_remove_assocations (ID object) {vector< objcassociation,objcallocator<objcassociation> > Elements {Associationsmanager Manager; Associationshashmap &associations (Manager.associations ()); if (associations.size () = = 0) return; disguised_ptr_t Disguised_object = Disguise (object); Associationshashmap::iterator i = Associations.find (disguised_object); if (i! = Associations.end ()) {//Copy all of the associations, need to be removed. Objectassociationmap *refs = i->second; for (Objectassociationmap::iterator j = refs->begin (), end = Refs->end (); J! = end; ++j) {ELEMENTS.P Ush_back (J->second); }//Remove the secondary table. Delete refs; Associations.erase (i); }}//The calls to Releasevalue () happen outside of the lock. For_each (Elements.begin (), Elements.end (), Releasevalue ());
The implementation also shows why it is not recommended in the introduction, because it iterates through all the associated objects and releases them all, which can cause other module functional defects.
Judging associated objects
It is interesting to determine whether an object has an associated object implementation.
inline bool objc_object::hasAssociatedObjects(){ if (isTaggedPointer()) return true; if (isa.nonpointer) return isa.has_assoc; return true;}
inline void objc_object::setHasAssociatedObjects(){ if (isTaggedPointer()) return; retry: isa_t oldisa = LoadExclusive(&isa.bits); isa_t newisa = oldisa; if (!newisa.nonpointer || newisa.has_assoc) { ClearExclusive(&isa.bits); return; } newisa.has_assoc = true; if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;}
By default, the result is true
that only one token bit is saved under a 64-bit system. So I'm guessing that to speed up the release cycle, when the destructor is being refactored, the object is determined whether it needs to be freed. Imagine that if you query the hash table every time, the execution efficiency will be reduced, it is better to pass first, then do the processing.
In the nonpointer
context of this article, it is briefly described as a 64-bit system where the pointer address is stored not only as a memory address, but also contains other token information, including the HAS_ASSOC involved in this article.
taggedPointer
is an optimization strategy that saves simple numeric or string information directly in the pointer address, thus not requesting additional memory to speed up the operation.
Summarize
The implementation of the associated object is not complex, it is saved in a global hash table, and access is performed by querying the table to find the association. The hash table is characterized by the sacrifice of space for time, so execution speed can also be guaranteed.
What is the application of question-and-answer related objects?
An association object can bind a lifetime variable to a specified object at run time.
1. Because it does not change the implementation of the original class, it can be extended to native or packaged libraries, generally with the category to achieve complete functionality.
Variables defined by the 2.ObjC class are exposed externally due to runtime characteristics, and the associated object can be used to hide key variables and ensure security.
3. Can be used for KVO, using the associated object as the observer, you can avoid observing itself causing the loop.
How does the system manage associated objects?
The system obtains the associated object through the management of a global hash table through the object pointer address and the fixed parameter address passed. setter
manage the life cycle of an object based on the parameter protocol passed in.
Is it necessary to manually empty the pointer when it is released?
When the object is disposed, if the protocol is set OBJC_ASSOCIATION_ASSIGN
, then his associated object does not reduce the reference count, and the other protocols are reduced to release the associated object.
unsafe_unretain
It is generally considered that the external object is controlled, so the object is not processed, so no matter what the protocol, the object frees without having to manually tell the associated object to empty.
Associatedobject Associative object principle implementation