methodListThe data structure

realizeClassWithoutSwift -> methodizeClass(Attach categories)

// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods(a);Copy the code

The LLDB debug command is usually used to view the specific contents inside**p $2.get(0).big()**To see the get function

    Element& getOrEnd(uint32_t i) const { 
        ASSERT(i <= count);
        return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
    }
    Element& get(uint32_t i) const { 
        ASSERT(i < count);
        return getOrEnd(i);
    }
Copy the code

So we know that method_list_t is storing Pointers

Whether sorting is required for classification loading

We know from the above that methods are pointer data, and Pointers themselves have no sort. So the categories themselves don’t need to be sorted

    auto const methods = cls->data() - >methods(a);for (auto mlists = methods.beginLists(),
              end = methods.endLists(a); mlists ! = end; ++mlists) {method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }
Copy the code

Exention vs Category

category:

  • Specifically for adding new methods to classes
  • You cannot add a member attribute to a class, nor can you get it if you add a member variable
  • You can add properties to classes through Runtime
  • Properties decorated with property in a class generate only getter and setter declarations, not member variables and method implementations with _

extention:

  • It’s kind of a special classification, anonymous classification
  • You can add member attributes to a class but keep the variables private
  • You can add methods to classes that are also private methods

Class extensions

Write a class extension in main. Clang find the main. CPP file open. The methods in the class extension and the set/get methods are found in the main class method list.

static struct/ * _method_list_t* / {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[18].
} _OBJC_$_INSTANCE_METHODS_LGStudent __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	18,
	{(struct objc_selector *)"ext_instanceMethod"."v16@0:8", (void *)_I_LGStudent_ext_instanceMethod},
	{(struct objc_selector *)"ext_name"."@ @ 0:8 16", (void *)_I_LGStudent_ext_name},
	{(struct objc_selector *)"setExt_name:"."v24@0:8@16", (void *)_I_LGStudent_setExt_name_},
	{(struct objc_selector *)"name"."@ @ 0:8 16", (void *)_I_LGStudent_name},
	{(struct objc_selector *)"setName:"."v24@0:8@16", (void *)_I_LGStudent_setName_},
	{(struct objc_selector *)"age"."i16@0:8", (void *)_I_LGStudent_age},
	{(struct objc_selector *)"setAge:"."v20@0:8i16", (void *)_I_LGStudent_setAge_},
	{(struct objc_selector *)"ext_name"."@ @ 0:8 16", (void *)_I_LGStudent_ext_name}}
}
Copy the code

We know that classification can affect the loading process of main classes, but what about class extensions, also in the source coderealizeClassWithoutSwiftIn the debuggingIt’s not hard to figure out that the extended method has been added to the method list.

classification

When we add attributes to the category, we get a warning like this, requiring us to manually implement the set and get methods

- (void)setCate_name:(NSString *)cate_name{
    /** 1: object 2: identifier 3: value 4: policy */
    objc_setAssociatedObject(self."cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)cate_name{
    return  objc_getAssociatedObject(self."cate_name");
}
Copy the code

associationsobjc_setAssociatedObjectSet the value

Objc_setAssociatedObject -> _object_set_associative_Reference The essence of an associated object is to store this property

bool isFirstAssociation = false;
    {
        // 1. Create an AssociationsManager management class
        AssociationsManager manager;
        // 2. Obtain the unique global static hash map
        AssociationsHashMap &associations(manager.get());
        // 3. Determine whether the inserted key value exists, such as "name"
        if (value) {
            // 4. Create an empty ObjectAssociationMap to retrieve the queried key-value pair
            // 5. Try_emplace If the key(name) is not found, create an empty TheBucket and return
            // 6. The try_emplace tag object has an associated object
            Refs_result first{Ptr,End} second = true
            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 */
            /* refs_result.first->second { Buckets = nil NumEntries = 0 NumTombstones = 0 NumBuckets = 0 } */
            auto &refs = refs_result.first->second;
            // 7. Try_emplace Replace the vacancy of BucketT with the ObjectAssociation composed of the current association (policy and value)
            // 8. Try_emplace tag ObjectAssociationMap first=false
            auto result = refs.try_emplace(key, std::move(association));
            /* refs_result.first->second { Buckets = 0x0000000101313590 NumEntries = 1 NumTombstones = 0 NumBuckets = 4 } */
            // Buckets have already been assigned
            if(! result.second) { association.swap(result.first->second); }}else {
            // 4. The iterative query in AssociationsHashMap is found according to DisguisedPtr
            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);
                    / / 5. Clean up
                    refs.erase(it);
                    //6. Clean up if the insert is empty
                    if (refs.size() = =0) {
                        associations.erase(refs_it);
                    }
                }
            }
        }
    }

Copy the code

LookupBucketFor algorithm

Try_emplace -> LookupBucketFor is the same as the search algorithm for bucket in the cache

template<typename LookupKeyT>
  bool LookupBucketFor(const LookupKeyT &Val,
                       const BucketT *&FoundBucket) const {
    // ...
	// Find the index of the bucket
    unsigned BucketNo = getHashValue(Val) & (NumBuckets- 1);
    unsigned ProbeAmt = 1;
    while (true) {
      const BucketT *ThisBucket = BucketsPtr + BucketNo;
      // Found Val's bucket? If so, return it
      if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
        FoundBucket = ThisBucket;
        return true;
      }

      // If an empty bucket is found, the key does not exist in the collection. Insert it and return the default value
      if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
        // If we've already seen a tombstone while probing, fill it in instead
        // of the empty bucket we eventually probed to.
        FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
        return false;
      }
      if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) && ! FoundTombstone) FoundTombstone = ThisBucket;// Remember the first tombstone found.
      if (ValueInfoT::isPurgeable(ThisBucket->getSecond() &&! FoundTombstone) FoundTombstone = ThisBucket;if (ProbeAmt > NumBuckets) {
        FatalCorruptHashTables(BucketsPtr, NumBuckets);
      }
      BucketNo += ProbeAmt++;
      BucketNo &= (NumBuckets- 1); / / to hash}}Copy the code

associationsobjc_getAssociatedObjectThe values

objc_getAssociatedObject -> _object_get_associative_reference

    {
        //1. Create an AssociationsManager management class
        AssociationsManager manager;
        //2. Obtain the unique global static hash map
        AssociationsHashMap &associations(manager.get());
        //3. The iterative query in AssociationsHashMap is found according to DisguisedPtr
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        /* associations.end() = { Ptr = 0x0000000100673cc0 End = 0x0000000100673cc0 } */
        if(i ! = associations.end()) {
            /* i->second = { Buckets = 0x0000000101b3c4f0 NumEntries = 1 NumTombstones = 0 NumBuckets = 4 } */
            //4. If the iterator is not the last to retrieve the ObjectAssociationMap
            ObjectAssociationMap &refs = i->second;
            // 5. Find the iterated query for ObjectAssociationMap
            ObjectAssociationMap::iterator j = refs.find(key);
            if(j ! = refs.end()) {
                association = j->second;
                // 6. Return the value modified by the attribute modifier
                association.retainReturnedValue(a); }}}Copy the code

conclusion

It’s a two-layer hash map, and it’s a two-layer hash map.

supplement

Constructor and destructor

class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    static Storage _mapStorage;

public:
    AssociationsManager()   { AssociationsManagerLock.lock(a); } ~AssociationsManager()  { AssociationsManagerLock.unlock(a); }AssociationsHashMap &get(a) {
        return _mapStorage.get(a); }static void init(a) {
        _mapStorage.init();
    }
};
Copy the code

The constructor AssociationsManager() is called. The destructor is automatically called when the constructor is out of scope.