preface
AssociationedObject is used mostly to extend member variables for a particular class in a Category, but also to dynamically create member variables for certain objects at run time. AssociationedObject is kind of a special kind of member variable. The purpose of this article is to explain in detail how AssociationedObject is implemented.
Relevant methods
objc_AssociationPolicy
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
};
Copy the code
This is the argument you need to use when calling objc_setAssociatedObject to specify the reference policy for the associated parameters.
-
OBJC_ASSOCIATION_ASSIGN
The associated reference is described as a weak reference
-
OBJC_ASSOCIATION_RETAIN_NONATOMIC
The associative reference is described as strong and nonatomic.
-
OBJC_ASSOCIATION_COPY_NONATOMIC
The associative reference is described as a copy reference and is nonatomic.
-
OBJC_ASSOCIATION_RETAIN
The associative reference is described as strong and atomic.
-
OBJC_ASSOCIATION_COPY
An associative reference is expressed as a copy reference and is atomic.
Just like with a Property, you can set the reference description for an associated reference.
objc_setAssociatedObject
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
Copy the code
Methods to create and set an associated object.
An associated object can be generated by attaching a selected value to the target object in the form of key-value and specifying the associated policy.
An existing associated object can be cleared by setting the value of the Key specified on the target object to nil.
objc_getAssociatedObject
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
Copy the code
Method of obtaining the value of the associated object.
Through the association Key, in the target object (object), get the value of the associated object (return value).
objc_removeAssociatedObjects
/** * Removes all associations for a given object. * * @param object An object that maintains associated objects. * * Note The main purpose of this function is to make it easy to return an object * to a "pristine state" use this function for general removal of * associations from objects, since it also removes associations that other clients * may have added to the object. Typically you should use \c objc_setAssociatedObject * with a nil value to clear an association. * * @see objc_setAssociatedObject * @see objc_getAssociatedObject */
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
Copy the code
Removes all associated objects of the target object (objCT).
The main purpose of this method is to make it easier for the object to return to its original state. Calling this method will clear all associated objects bound to the target object. If other developers also have an object associated with this object, or if you have a different object associated with it in another module, this method will be removed altogether. Usually to clear an associated object, set the associated object to nil by objc_setAssociatedObject.
The underlying implementation
On the underlying implementation, there is a GC version and a gC-free version. GC is the MacOS garbage collection mechanism, but it is now deprecated and ARC is recommended. On iOS, there is only MRC and ARC, so only the GC-free version of the code is captured here.
objc_setAssociatedObject
In this method, there are a lot of data structures, first introduce the data structure, to help understand, you can also go down to look at the main process, there are not clear and then come back to find.
AssociationsManager
// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock, and calling its assocations()
// method lazily allocates the hash table.
spinlock_t AssociationsManagerLock;
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return*_map; }}; AssociationsHashMap *AssociationsManager::_map =NULL;
Copy the code
The AssociationsManager function manages the lock, which is locked when it is created and released when it is destroyed. There is also a global variable, _map, that manages the relationship between the target object and the HashMap (key-value Pointers).
AssociationsManager has a _map reference to the global variable of type AssociationsHashMap.
AssociationsHashMap provides an lvalue reference method to create an AssociationsHashMap. _map is a reference, *_map is a dereferencing and becomes an AssociationsHashMap object. The AssociationsHashMap object reference address is returned.
A reference to a global variable of type AssociationsHashMap is also declared.
AssociationsHashMap
typedef ObjcAllocator<std::pair<const disguised_ptr_t, ObjectAssociationMap*> > AssociationsHashMapAllocator;
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void*ptr) { ::free(ptr); }};// Generic definition of unordered_map
template <class _Key,
class _Tp,
class _Hash = hash<_Key>,
class _Pred = equal_to<_Key>,
class _Alloc = allocator<pair<const _Key, _Tp> > >
Copy the code
Disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, unordered_map DisguisedPointerEqual, AssociationsHashMapAllocator>
Explain what this generic means
- _Key (key_type) corresponds disguised_ptr_T, which is actually of unsigned long type, which is used to hold the pointer.
- _Tp (mapped_type) corresponds to ObjectAssociationMap *, which indicates the type of values stored in the Map. ObjectAssociationMap is described later.
- _Hash (hasher) corresponds to DisguisedPointerHash, which provides the hash algorithm.
- _Pred (key_equal) corresponds to DisguisedPointerEqual, which provides the equal algorithm (if the two Pointers are the same, they are equal).
- _Alloc (allocator_type) corresponds to the constructor type, here the constructor type is ObjcAllocator< STD ::pair
>, STD ::pair disguised_pTR_t And Value is the key-value pair of ObjectAssociationMap *.
It implements create and delete functions.
disguised_ptr_t
typedef unsigned long uintptr_t;
typedef uintptr_t disguised_ptr_t;
inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }
Copy the code
It can be seen here that disguised_ptr_t is actually an unsigned long, which has the same length as the pointer, so it is used as the pointer. ~ uintPtr_t (value) : Value is also an object pointer wrapped as an unsigned long and returned bit-for-bit. Id (~ DPTR) : The disguISed_pTR_T bit by bit is inverted and the object (ID) pointer is returned.
ObjectAssociationMap
typedef ObjcAllocator<std::pair<void * const, ObjcAssociation> > ObjectAssociationMapAllocator;
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void*ptr) { ::free(ptr); }};// Generic definition of STD ::map
template <class _Key,
class _Tp,
class _Compare = less<_Key>,
class _Allocator = allocator<pair<const _Key, _Tp> > >
Copy the code
Similar to AssociationsHashMap, ObjectAssociationMap inherited from public STD: : map (void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator >
Generic means:
-
_key (key_type) corresponds to void *, an untyped pointer, which represents the memory address.
-
_Tp (mapped_type) corresponds to [ObjcAssociation](#### ObjcAssociation), mentioned above, which holds the value of the associated object and the associated policy.
-
_Compare (key_compare) corresponds to ObjectPointerLess and provides a comparison algorithm (comparing pointer addresses).
-
_Allocator (allocator_type) is ObjcAllocator< STD ::pair<void * const, ObjcAssociation> >, The Key is void * const (memory address) and the Value is the key-value pair of the ObjcAssociation.
ObjcAssociation
class ObjcAssociation {
uintptr_t _policy;
id _value;
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}
uintptr_t policy() const { return _policy; }
id value() const { return _value; }
bool hasValue() { return_value ! =nil; }};Copy the code
This is the structure of the associated object that stores the associated policy _policy and the value _value of the associated object.
acquireValue
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;
}
Copy the code
The reference policy is determined. If it is Retian, the reference count of value is increased by one by objc_retain. If it is Copy, the Copy method of value is called to generate a new Copy.
setHasAssociatedObjects
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;
}
Copy the code
The main thing to do here is to set the HAS_ASSOC flag bit in ISA to true.
Tagged Pointer
This is where Tagged Pointer comes in. Tagged Pointer is a strategy introduced in 64-bit systems that stores the data of a Pointer object directly in a Pointer in order to save memory.
For example, in a 64-bit system, a Pointer is 8 bytes long. When the value associated with the Pointer is less than 8 bits, the system converts the Pointer to Tagged Pointer and adds a TaggedPoint to the last bit. The pointer structure becomes the “0x store data +TaggedPoint identifier” structure.
0 xb000000000000032 | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | 0 x | -- - | |. Data will be storedCopy the code
The benefit of this is that the memory footprint is reduced, and since the data does not need to be put into the heap, there is no need for malloc and free, so both read and run speeds are improved.
At this point the pointer has become a value, not an address, so it doesn’t have an ISA pointer either, so make a judgment first.
ReleaseValue and ReleaseValue
struct ReleaseValue {
voidoperator() (ObjcAssociation &association) { releaseValue(association.value(), association.policy()); }};static void releaseValue(id value, uintptr_t policy) {
if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
returnobjc_release(value); }}Copy the code
Here is the code associated with the release of the associated object. If the reference policy is Retian, the release will subtract one from the reference count.
The main implementation
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// Create an ObjcAssociation object and initialize policy to OBJC_ASSOCIATION_ASSIGN and value to nil.
ObjcAssociation old_association(0.nil);
// Process the value according to the reference policy. If Copy, call the Copy method of value to get new_value. If Retain, hold value. The reference count of value is increased by one.
id new_value = value ? acquireValue(value, policy) : nil;
{
// Create an AssociationsManager that is locked when initialized and unlocked when destroyed.
// C++ initializes the object by default, that is, it is already locked.
AssociationsManager manager;
// Get a reference to the global variable AssociationsHashMap in AssociationsManager,
// Call the copy constructor of AssociationsHashMap
// Directly initialize the new associations variable with the reference above.
AssociationsHashMap &associations(manager.associations());
// Creates a DISGUised_ptr_t, calls DISGUISE(object) and uses the target object to initialize.
disguised_ptr_t disguised_object = DISGUISE(object);
// If new_value is not nil, the scenario is either to set the new association value or to change the association value.
if (new_value) {
// Look in AssociationsHashMap by iterator,
// ObjectAssociationMap*(I) corresponding with disguised_ptr_t
// Note: Map is composed of STD ::pair
.
AssociationsHashMap::iterator i = associations.find(disguised_object);
// If ObjectAssociationMap* (I) is found in the Map, the associated value is changed.
// note: in C++ maps, we return map.end() if no iterator is found;
if(i ! = associations.end()) {ObjectAssociationMap* (refs)
// Second to AssociationsHashMap (I),
// This is the Value of the Map.
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap* (refs)
// To find the corresponding ObjectAssociationMap object (j).
ObjectAssociationMap::iterator j = refs->find(key);
// If the ObjcAssociation (j) is found.
// Map: STD ::pair
if(j ! = refs->end()) {// Set the value of ObjectAssociationMap,
// ObjcAssociation, assign to old_association
old_association = j->second;
// Update the Map Value to the newly built ObjcAssociation.
j->second = ObjcAssociation(policy, new_value);
} else {
// If j is the only element in the container,
// Set a Map for the ObjectAssociationMap reference (*refs),
// Key is the passed key and Value is the newly built ObjcAssociation.(*refs)[key] = ObjcAssociation(policy, new_value); }}else {
// If Object is the only element in the container
// Create an ObjectAssociationMap
ObjectAssociationMap *refs = new ObjectAssociationMap;
// In AssociationsHashMap(associations),
// The DISGUISed_object is stored using Key.
associations[disguised_object] = refs;
// Create ObjectAssociationMap with Key as Key,
// Value is the newly built ObjcAssociation.
(*refs)[key] = ObjcAssociation(policy, new_value);
// Call the setHasAssociatedObjects method on the associated target object.
// Set has_assoc in the object's ISA to true
// Indicates that this object has an associated object.
// This method is in objc-objct.object->setHasAssociatedObjects(); }}else {
// when new_value = nil,
// Look up the corresponding ObjectAssociationMap (I) through disguised_object
AssociationsHashMap::iterator i = associations.find(disguised_object);
// If ObjectAssociationMap (I) is found
if(i ! = associations.end()) {ObjectAssociationMap *refs
ObjectAssociationMap *refs = i->second;
// Find ObjcAssociation (j) with the key passed in
ObjectAssociationMap::iterator j = refs->find(key);
// If you find ObjcAssociation (j)
if(j ! = refs->end()) {// Take out the old value and assign it to old_association.
old_association = j->second;
// Remove ObjcAssociation (j) from ObjectAssociationMap.refs->erase(j); }}}// Release the lock here.
}
// If the old value exists, the old value is released.
if (old_association.hasValue()) ReleaseValue()(old_association);
}
Copy the code
The storage structure of the associated object
You can see here a summary of the storage structure of associations.
AssociationsHashMap manages the relationship between object and ObjectAssociationMap
The ObjectAssociationMap is the relationship between management Key and ObjectAssociation (associated object).
objc_getAssociatedObject
The following analysis is about the value of the associated object logic, most of the structure part in the set value part are said, but encountered do not understand the back to see, here directly will be the main implementation.
The main implementation
id _object_get_associative_reference(id object, void *key) {
// Create a value.
id value = nil;
// The default reference policy is assign.
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
// Create an AssociationsManager and lock it.
AssociationsManager manager;
// Obtain the global AssociationsHashMap.
AssociationsHashMap &associations(manager.associations());
// The DISGUISed_object is built through object.
disguised_ptr_t disguised_object = DISGUISE(object);
// Looks up the corresponding ObjectAssociationMap according to disguised_object.
AssociationsHashMap::iterator i = associations.find(disguised_object);
// If ObjectAssociationMap is found.
if(i ! = associations.end()) {// Extract ObjectAssociationMap.
ObjectAssociationMap *refs = i->second;
// Find ObjcAssociation based on key.
ObjectAssociationMap::iterator j = refs->find(key);
// If ObjcAssociation exists.
if(j ! = refs->end()) {/ / get ObjcAssociation
ObjcAssociation &entry = j->second;
// Set value to ObjcAssociation value.
value = entry.value();
// Set the policy to the ObjcAssociation policy.
policy = entry.policy();
// If the reference policy is Retain mode
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
// Call the retian method of value, incrementing the reference count by one.objc_retain(value); }}}/ / releases the lock
}
// If the value exists and the reference policy is AutoRelease mode.
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
// Call the autoRelease method of value and set value to autoRelease.
objc_autorelease(value);
}
// Return the obtained associated object
return value;
}
Copy the code
objc_retain
id
objc_retain(id obj)
{
if(! obj)return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain(a); }Copy the code
And in The Case of Retian, we also decided that it was Tagged Pointer.
Obj’s retian method is called once, and the reference count is incremented by one.
objec_autorelease
id
objc_autorelease(id obj)
{
if(! obj)return obj;
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
Copy the code
Almost identical to Retian, obj’s autoRelease method is called.
objc_removeAssociatedObjects
void _object_remove_assocations(id object) {
// Declare a vector with key of ObjcAssociation and value of ObjcAllocator
. This is a constructor structure that will be used internally at Runtime.
// vector is a dynamically sequential container in C++.
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
// Create an AssociationsManager and lock it.
AssociationsManager manager;
// Obtain AssociationsHashMap.
AssociationsHashMap &associations(manager.associations());
// If AssociationsHashMap does not contain no element, the process is terminated.
if (associations.size() == 0) return;
// The disguised_object is constructed through object.
disguised_ptr_t disguised_object = DISGUISE(object);
// Look for the ObjectAssociationMap corresponding to disguised_object in AssociationsHashMap.
AssociationsHashMap::iterator i = associations.find(disguised_object);
// If ObjectAssociationMap exists.
if(i ! = associations.end()) {// Get ObjectAssociationMap.
ObjectAssociationMap *refs = i->second;
// Iterate over ObjectAssociationMap
for(ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j ! = end; ++j) {// Store all objectAssociations in the vector.
elements.push_back(j->second);
}
// Free the memory space of ObjectAssociationMap.
delete refs;
// Delete the ObjectAssociationMap element from AssociationsHashMap.associations.erase(i); }}// Loop to release each ObjectAssociation held in the vector
for_each(elements.begin(), elements.end(), ReleaseValue());
}
Copy the code
The life cycle
// File: runtime.h
/** * Destroys an instance of a class without freeing memory and removes any * associated references this instance might Have had. * Destroys an instance of the class without freeing memory and removes any associated references that the instance may have. * * @param obj The class instance to destroy. * * @return \e obj. Does nothing if \e obj is nil. * * @note CF and other clients do call this under GC. */
OBJC_EXPORT void * _Nullable objc_destructInstance(id _Nullable obj)
OBJC_AVAILABLE(10.6.3.0.9.0.1.0.2.0)
OBJC_ARC_UNAVAILABLE;
/*********************************************************************** * object_dispose * fixme * Locking: None file: objc runtime - new. M * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
id
object_dispose(id obj)
{
if(! obj)return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
/ / = = = = = = = = = = = = = = = file: objc - private. H = = = = = = = = = = = = = = = = = = = = = = = =
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if(fastpath(isa.nonpointer && ! isa.weakly_referenced && ! isa.has_assoc && ! isa.has_cxx_dtor && ! isa.has_sidetable_rc)) { assert(! sidetable_present()); free(this);
}
else {
object_dispose((id)this); }}/ / = = = = = = = = = = = = = = = file: NSObject. Mm = = = = = = = = = = = = = = = = = = = = = = = =
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
Copy the code
As you can see from the comments, when NSObject executes dealloc, it cleans up the associated objects and doesn’t need to maintain them manually.
conclusion
- We can see that Runtime manages the associated objects thread-safe, adding, deleting, checking, and changing them with locks.
- AssociationsHashMap manages all associated objects that are added to objects while the App is running.
- When NSObject dealloc, it releases the associated objects it holds.