Blog links understand association properties from source code

In the class, we use @Property (nonatomic, copy) NSString *name to generate a property. It does three things:

  1. Declare a_nameThe variables;
  2. The statementsetName:andgetNameMethods;
  3. Default implementations of setter and getter methods;

However, when writing a property in a class, it only declares setter and getter methods. It does not generate member variables and implement setter and getter methods. Therefore, if you want to implement a property in a class, you have to use the associative object approach.

Use of associated objects

First of all, why do we use associative objects? @property does not automatically generate instance variables and access methods in a class, and member variables cannot be declared in a class. From a source point of view, categories have no array of member variables in the compile-time structure they generate. For these reasons, if we want to achieve the same effect as an attribute in a class, we use associative objects.

The application of the associated object is as follows:

// .h
@interface FatherA : NSObject

@property (nonatomic, copy) NSString *name;

@end

// .m
@implementation FatherA

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self,
                             @selector(name),
                             name,
                             OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, _cmd);
}

@end
Copy the code

Implementation of associated objects

Here’s the source code for ObjC4-750.1, which you can download here. The apis we use for associated objects are as follows:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);
Copy the code

The functions of these three methods are as follows:

  • Add an associated object as a key-value pair
  • Gets the associated object based on the key
  • Remove all associated objects

The core object of the associated object

Before looking at the API implementation of the association object, let’s look at the core object of the association object.

AssociationsManager

AssociationsManager is defined as follows:

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

AssociationsManager will maintain a AssociationsHashMap, at the time of initialization, call AssociationsManagerLock. The lock (), In the destructor will call AssociationsManagerLock. Unlock (), and associations to obtain a global AssociationsHashMap.

In addition, the AssociationsManager uses a spinlock_t AssociationsManagerLock to ensure that operations on the AssociationsHashMap are thread-safe.

AssociationsHashMap

HashMap is equivalent to NSDictionary in OC. AssociationsHashMap is defined as follows:

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); }};Copy the code

AssociationsHashMap inherits from unordered_map and uses C++ syntax. Its function is to save the mapping from the object’s DISGUised_ptr_T to ObjectAssociationMap. AssociationsHashMap stores several ObjectAssociationMaps in the form of key-value.

ObjectAssociationMap

ObjectAssociationMap is defined as follows:

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); }};Copy the code

ObjectAssociationMap stores the mapping from key to Objcasassociation. We can understand that ObjectAssociationMap stores several Objcasassociation objects in key-value form. This data structure holds all associated objects corresponding to the current object:

ObjcAssociation

ObjcAssociation is defined as follows:

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

The ObjcAssociation object holds its associated objects. The _policy and _value fields hold the policy and value passed in when we use objc_setAssociatedObject.

objc_setAssociatedObject

With the objc_setAssociatedObject function, we add an associated object, which is implemented as follows:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}
Copy the code

Take a look at the implementation of _object_set_associative_reference:

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (ifAny) outside the lock. // Create an ObjcAssociation local variable that holds the original associated object and finally frees ObjcAssociation old_association(0, nil); // Call acquireValue to retain new_value or copy id new_value = value? acquireValue(value, policy) : nil; {// Initialize an AssociationsManager and get AssociationsHashMap AssociationsManager; AssociationsHashMap &associations(manager.associations()); // DISGUised_ptr_t is the key in AssociationsHashMap, which is obtained through the passed object DISGUised_ptr_T DISGUised_object = DISGUISE(object); // new_value Sets or updates the value of an associated object if it has a value; otherwise, it deletes an associated objectif (new_value) {
            // breakAny existing association. / / find ObjectAssociationMap AssociationsHashMap: : iterator I = associations.find(disguised_object);if(i ! = associations. End ()) {// Secondary table exists // ObjectAssociationMap exists // Check whether the key exists. End () 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 does not exist // Initialize an ObjectAssociationMap, Then instantiate the ObjcAssociation object and add it to the Map and callset/ / HasAssociatedObjects functionsetHasAssociatedObjects indicates that the current class has an associated class // it marks has_ASsoc in the ISA structure astrue
                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. / / find ObjectAssociationMap AssociationsHashMap: : iterator I = associations.find(disguised_object);if(i ! = associations.end()) { ObjectAssociationMap *refs = i->second; // ObjectAssociationMap exists // Check whether key exists. Key is call the erase function to delete ObjectAssociationMap key corresponding to the node 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). // Call ReleaseValue to release the value of the original associated objectif (old_association.hasValue()) ReleaseValue()(old_association);
}
Copy the code

You can see the flow of objc_setAssociatedObject through the source code and comments above, and then we use a picture to illustrate how the associated objects work:

objc_getAssociatedObject

Objc_getAssociatedObject is easy to understand once you understand the implementation of objc_setAssociatedObject as follows:

id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}
Copy the code

Then look at the implementation of _object_get_associative_reference:

id _object_get_associative_reference(id object, void *key) { id value = nil; uintptr_t policy = OBJC_ASSOCIATION_ASSIGN; {// Initialize an AssociationsManager and get AssociationsHashMap AssociationsManager; AssociationsHashMap &associations(manager.associations()); // The DISGUised_ptr_T is obtained through object, which is used as the key disguised_ptr_T DISGUised_object = DISGUISE(object) in AssociationsHashMap; The location of the / / find ObjectAssociationMap AssociationsHashMap: : iterator I = associations. The find (disguised_object);if(i ! = associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); // Find the objCasSociety objectif(j ! = refs->end()) { ObjcAssociation &entry = j->second; value = entry.value(); policy = entry.policy(); // Indicates a strong type, retain operationif(policy & OBJC_ASSOCIATION_GETTER_RETAIN) { objc_retain(value); }}}}if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        // autorelease
        objc_autorelease(value);
    }
    return value;
}
Copy the code

objc_removeAssociatedObjects

Objc_removeAssociatedObjects is used to remove all associated objects.

void objc_removeAssociatedObjects(id object) 
{
    if(object && object->hasAssociatedObjects()) { _object_remove_assocations(object); }}Copy the code

The hasAssociatedObjects function determines whether there are associated objects. If so, _object_remove_assocations is called.

The implementation of objc_setAssociatedObject says that if the ObjectAssociationMap does not exist when an associated object is added, an ObjectAssociationMap is initialized. Instantiate the ObjcasSociety object and add it to the Map, and call the setHasAssociatedObjects function. The setHasAssociatedObjects function is used to mark has_ASsoc in the ISA structure as true, and the hasAssociatedObjects function is used to get the result of that flag bit.

Then look at _object_remove_assocations, which implements the following:

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 that need to be removed. ObjectAssociationMap *refs = i->second;for(ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j ! = end; ++j) { elements.push_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()); }Copy the code

_object_remove_ASsocations adds all the associated objects contained in the object into a vector, removes the corresponding nodes in the AssociationsHashMap, ReleaseValue() is then called on all ObjcAssociation objects to release values that are no longer needed.

conclusion

Implementation of associated objects

  • The essence of an associative object is thatObjcAssociationObject;
  • ObjectAssociationMapIn order tokeyData structure for storing associated objects for keys (ObjcAssociationObject);
  • There’s one for every objectObjectAssociationMap, objecthas_assocUsed to determine whether there are associated objects, and objects andObjectAssociationMapThe mapping between is stored inAssociationsHashMap;
  • AssociationsHashMapGlobally unique. YesAssociationsManagerManagement.

Whether attributes can be implemented in a classification

If attributes are viewed as instance variables, the answer is no. If attributes are viewed as collections of access methods and stored values, then classification can implement attributes. Personally, I prefer the former.

An associated object of type weak

There is no weak type strategy in relation objects, and in development there is really very little talk about using weakly typed relation objects, except for the purpose of using them. As I understand it, since it is called an associated object, it must be associated with its own life cycle before it can be associated. Weak means that the object has no connection with its own life cycle, and its release will not affect the associated object. In conclusion, I think the association object of the weak type is meaningless.

However, if you want to implement an association object of weak type, you can wrap it with an intermediate object. The code is as follows:

#pragma mark - Weak Associated Object

@interface _NNWeakAssociatedWrapper : NSObject

@property (nonatomic, weak) id associatedObject;

@end

@implementation _NNWeakAssociatedWrapper

@end

void nn_objc_setWeakAssociatedObject(id object, const void * key, id value) {
    _NNWeakAssociatedWrapper *wrapper = objc_getAssociatedObject(object, key);
    if(! wrapper) { wrapper = [_NNWeakAssociatedWrapper new]; objc_setAssociatedObject(object, key, wrapper, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } wrapper.associatedObject = value; } id nn_objc_getWeakAssociatedObject(id object, const void * key) { id wrapper = objc_getAssociatedObject(object, key); id objc = wrapper && [wrapper isKindOfClass:_NNWeakAssociatedWrapper.class] ? [(_NNWeakAssociatedWrapper *)wrapper associatedObject] : nil;return objc;
}
Copy the code