Study harmoniously! Don’t be impatient!! I’m your old friend, Xiao Qinglong

To undertake the article iOS underlying analysis of the load (below) to do some supplements.

Ask probing questions

The loading of classified data has been analyzed in the previous article, but some finishing touches have not been explained. For example, the classification added attributes, but the specific implementation does not provide, so that the warning:

So that when I directly assign a value to a category attribute, it tells me that I didn’t implement setName_category:

In that case, let’s implement it ourselves as required:

As shown in the figure above, objc_setAssociatedObject and objc_getAssociatedObject are used to implement the set and GET methods, and the properties can be assigned and accessed normally.

We know that the category data is dynamically loaded into the class list at runtime, and the set and GET methods are not generated by the system for us, so we need to add them manually. But how do objc_setAssociatedObject and objc_getAssociatedObject do data binding? That’s the point we’re going to explore.

To explore – objc_setAssociatedObject

Because the current project is objC source code, so direct command+ stand-alone access step by step, finally located to: objc_setAssociatedObject -> _object_set_associative_reference,

// object Specifies the instance object to which the property is associated
// Name of the key attribute
// value Specifies the attribute value
// policy Indicates the association policy
/// point of inquiry: how are attribute values bound to class instance objects
/// Bind value to object by key. The binding policy is policy
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy){...DisguisedPtr< objC_Object > is used as the DisguisedPtr
      
        data structure
      
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    
    // 2. Store policy and value to associationObjcAssociation association{policy, value}; . bool isFirstAssociation =false;
    {
        / / / management class
        AssociationsManager manager;
        Hash table AssociationsHashMap ()
        AssociationsHashMap &associations(manager.get());

        if (value) {
            Try_emplace creates an empty BucketT the first time try_emplace is called
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                isFirstAssociation = true;
            }

            /* establish or replace the association */
            auto &refs = refs_result.first->second;
            /// 5. Call try_emplace again and insert association into BucketT
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else {
            /// if value is null, erase
            auto refs_it = associations.find(disguised);
            if(refs_it ! = associations.end()) { auto &refs = refs_it->second; auto it = refs.find(key);if(it ! = refs.end()) { association.swap(it->second); refs.erase(it);if (refs.size() == 0) {
                        associations.erase(refs_it);

                    }
                }
            }
        }
    }
    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // Release old values (lock outside).
    association.releaseHeldValue();
}
Copy the code
Call try_emplace for the first time

We found that there are two functions named LookupBucketFor:

  1. Second parameterWith cost
/** LookupBucketFor- Finds the appropriate bucket for Val and casts the bucket. Returns true if the bucket contains a key and a value, otherwise it returns a bucket with an empty marker or tombstone and returns false. * /
  template<typename LookupKeyT>
  bool LookupBucketFor(const LookupKeyT &Val,
                       const BucketT *&FoundBucket) const {
    
    printf("LookupBucketFor-------const-----const \n");
    const BucketT *BucketsPtr = getBuckets();
    const unsigned NumBuckets = getNumBuckets();

    if (NumBuckets == 0) {
      FoundBucket = nullptr;
      return false; }... }Copy the code
  1. Second parameterWith no cost
bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
    printf("LookupBucketFor-------const-----non \n");
    const BucketT *ConstFoundBucket;
    // LookupBucketFor is called, and the second argument is const
    bool Result = const_cast<const DenseMapBase *>(this)
      ->LookupBucketFor(Val, ConstFoundBucket);
    FoundBucket = const_cast<BucketT *>(ConstFoundBucket);
    return Result;
  }
Copy the code

From the code of the try_emplace function, it calls the second LookupBucketFor. Let’s run the project to see the order in which the console prints:

Printed structure, consistent with our analysis.

What did InsertIntoBucket do
template <typename KeyArg, typename... ValueArgs>
  BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key, ValueArgs &&... Values){ TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket); TheBucket->getFirst() = std::forward<KeyArg>(Key); : :new(&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...) ;return TheBucket;
  }
Copy the code

Let’s print out what the final bucket looks like:

We found that the InsertIntoBucket function, it inserts the Key and Values into TheBucket. Key and Values correspond to:

  • Key — Try_emplace functiontheThe first parameter
  • Values — Try_emplace functiontheSecond parameter

Here we can see that Values is empty the first time we call it. Moving on to the next step, after the first try_emplace function call, print refs_result:

Second is not inserted into the InsertIntoBucket function because values are empty.

Try_emplace is called a second time

On the second entry to try_emplace, we find that the second argument takes association (association is a wrapper around policy and value). We directly locate the InsertIntoBucket function:

As shown in the figure, Values are truly valued at this point. Next, print the result:

Why is AssociationsHashMap a singleton?

Location code:

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy){... AssociationsHashMap &associations(manager.get()); . }Copy the code

Command + single machine get:

Static modifier: For AssociationsManager, _mapStorage is unique, so AssociationsHashMap is a singleton.

To explore – objc_getAssociatedObject

Objc_getAssociatedObject – _object_get_associative_reference:

id
_object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};

    {
        AssociationsManager manager;/ / management class
        AssociationsHashMap &associations(manager.get());/// get all the hashMaps
        // find the corresponding HashMap based on object
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if(i ! = associations.end()) { ObjectAssociationMap &refs = i->second; ObjectAssociationMap::iterator j = refs.find(key);// Select * from bucket (j);
            if(j ! = refs.end()) {// get the association (association is the encapsulation of _policy and _value)
                association = j->second;
                / / / remove the Valueassociation.retainReturnedValue(); }}}return association.autoreleaseReturnedValue();
}
Copy the code

Print data at breakpoint:

Break point continues down:

This completes the exploration of objc_setAssociatedObject and objc_getAssociatedObject.

conclusion

Objc_setAssociatedObject process:

  1. Object is encapsulated into the DisguisedPtr structure

  2. Encapsulate the policy and value into an object Society structure

  3. Find the AssociationsHashMap hash table

  4. Call try_emplace

  5. If try_emplace is called for the first time, the empty BucketT is created

  6. Then call try_emplace again and insert Association into BucketT

Objc_getAssociatedObject process:

  1. Get management class

  2. Get all the HashMaps

  3. Find the corresponding HashMap based on object

  4. Find TheBucket according to key

  5. Take association out of TheBucket

  6. Retrieve value from Association

When was the supplement-association association released?

We know that association uses object to find the corresponding HashMap table and then operates on this table. Is the release of association also related to the life cycle of object? Quick location, global search “dealloc {” :

// deallocating we know, locating is true
void
_object_remove_assocations(id object, bool deallocating)
{
    ObjectAssociationMap refs{};
    {
    Objc_getAssociatedObject = objc_getAssociatedObject ();
    // The process is to find the associated object and perform erase.
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if(i ! = associations.end()) { refs.swap(i->second);// If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
            bool didReInsert = false;
            if(! deallocating) {// Because the parameter DealLocating is true, it won't be there. }if(! didReInsert)/ / / erasureassociations.erase(i); }}... }Copy the code

Let me put a diagram here

code

Link: pan.baidu.com/s/16oTTFgdB… Password: 5G27 — share from Baidu webdisk super member V2

update

When was the association released? (See article for details)