While we explored the class loading process in previous articles, this time we’ll look at two points of class relevance: class extensions and associated objects.

Class extensions

Clang compiler

@interface JSAnimal : NSObject
​
@property (nonatomic,copy)NSString *name;
​
- (void)sayWow;
​
@end
​
@interface JSAnimal ()
​
@property (nonatomic,copy)NSString *type;
​
- (void)ex_sayWow;
​
@end
​
@implementation JSAnimal
​
+ (void)classMethod{
    NSLog(@"%s",__func__);
}
​
- (void)sayWow{
    NSLog(@"%s",__func__);
}
​
- (void)ex_sayWow{
    NSLog(@"%s",__func__);
}
​
@end
​
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        JSAnimal *animal = [[JSAnimal alloc] init];
        [animal ex_sayWow];
        NSLog(@"done");
    }
    return 0;
}
Copy the code
clang -rewrite-objc main.m -o main.cpp
Copy the code

foundextensionIn the statementattributeandmethodsThe compiled andclassIn together, asclassThat is to sayProperties and methods in the extensioninCompile timeIs added to theThis classIn the now.

Explore the runtime through the source code

Define a JSPerson class and an extension that implements the methods declared in the extension.

​
@interface JSPerson : NSObject
​
@property (nonatomic, copy) NSString *name;
​
- (void)saySomething;
​
@end
​
#import "JSPerson.h"
​
@implementation JSPerson
​
+ (void)load{
    
}
​
- (void)saySomething{
    NSLog(@"%s",__func__);
}
​
- (void)ext_instanceMethod{
    NSLog(@"%s",__func__);
}
​
+ (void)ext_classMethod{
    NSLog(@"%s",__func__);
}
@end
Copy the code
@interface JSPerson ()
​
- (void)ext_instanceMethod;
​
+ (void)ext_classMethod;
​
@end
Copy the code

Note that the JSPerson class implements the load method in order to make it non-lazily loaded. Based on our experience, we added breakpoint debugging in realizeClassWithoutSwift

throughlldbprintroThe list of methods in

(lldb) p ro (const class_ro_t *) $0 = 0x0000000100004790 (lldb) p *$0 (const class_ro_t) $1 = { flags = 0 instanceStart = 8 instanceSize = 16 reserved = 0 = { ivarLayout = 0x0000000000000000 nonMetaclass = nil } name = { std::__1::atomic<const char *> = "JSPerson" { Value = 0x0000000100003b58 "JSPerson" } } baseMethodList = 0x00000001000047d8 baseProtocols = nil ivars = 0x0000000100004840 weakIvarLayout = 0x0000000000000000 baseProperties = 0x0000000100004868 _swiftMetadataInitializer_NEVER_USE = {} } (lldb) p $1.baseMethods() (method_list_t *) $2 = 0x00000001000047d8 (lldb) p *$2 (method_list_t) $3 = { entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 4) } (lldb) p $3.get(0).big() (method_t::big) $4 = { name = "saySomething" types = 0x0000000100003d84 "v16@0:8" imp = 0x0000000100003600 (KCObjcBuild`-[JSPerson saySomething]) } (lldb) p $3.get(1).big() (method_t::big) $5 = { name =  "ext_instanceMethod" types = 0x0000000100003d84 "v16@0:8" imp = 0x0000000100003630 (KCObjcBuild`-[JSPerson ext_instanceMethod]) } (lldb) p $3.get(2).big() (method_t::big) $6 = { name = "name" types = 0x0000000100003d98 "@16@0:8" imp = 0x0000000100003660 (KCObjcBuild`-[JSPerson name]) } (lldb) p $3.get(3).big() (method_t::big) $7 = { name  = "setName:" types = 0x0000000100003da0 "v24@0:8@16" imp = 0x0000000100003690 (KCObjcBuild`-[JSPerson setName:]) }Copy the code

As you can see, the direction in the extension is now loaded knowing that the methods in RO were determined at compile time, so it also verifies that the methods in the extension were added to this class at compile time.

summary

  • Class extension inCompile timeIt’s compiled with the class as part of the class
  • Class extensions are justThe statement, depends on theThis classThe implementation of the.

The associated object of the category

We know that you can’t add attributes to a class normally, but you can with an associated object, and this is done in two ways

  • throughobjc_setAssociatedObjectMethod to set the value.
  • throughobjc_getAssociatedObjectMethod value.

Let’s explore each of them.

Objc_setAssociatedObject process

Objc_setAssociatedObject takes four arguments:

  • Parameter 1: The object to be associated
  • Parameter 2: representation, easy to find identification
  • Parameter 3: value Indicates the value
  • Parameter 4: of the propertystrategy, we often use attributes such asnonatomic,strong,weak.

First define JSPerson’s classification by defining an attribute cateegory_name:

@interface JSPerson (JSCategory)
​
@property (nonatomic, copy) NSString *cateegory_name;
​
@end
@implementation JSPerson (JSCategory)
​
- (void)setCateegory_name:(NSString *)category_name{
    objc_setAssociatedObject(self, "category_name", category_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
​
- (NSString *)category_name{
    return  objc_getAssociatedObject(self, "category_name");
}
​
@end
Copy the code

Add breakpoints where the property is assigned to the main function, depending on the call

Locate the objc_setAssociatedObject method

The _object_set_associative_reference method is called. Let’s follow up and check the source code:

void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) { // This code used to work when nil was passed for object and key. Some code // probably relies on that to not crash. Check and handle it explicitly. // rdar://problem/44094390 if (! object && ! value) return; if (object->getIsa()->forbidsAssociatedObjects()) _objc_fatal("objc_setAssociatedObject called on instance (%p) of class  %s which does not allow associated objects", object, object_getClassName(object)); /// Encapsulate the object as DisguisedPtr DisguisedPtr< objC_Object > Premiere6.0 {(objc_Object *)object}; /// Wrap policy value ObjcAssociation association{policy, value}; // retain the new value (if any) outside the lock. // Process according to the policy type (strong, weak, etc.). bool isFirstAssociation = false; {// Initialize the AssociationsManager variable, which is equivalent to automatically calling the constructor of AssociationsManager to initialize AssociationsManager; /// A HashMap AssociationsHashMap &associations(manager.get()); If (value) {// The result returned is a class pair auto refs_result = associations. Try_emplace (Overlap, 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; Auto result = refs.try_emplace(key, STD ::move(association)); auto result = refs.try_emplace(key, STD ::move(association)); // Check whether the current key has an association object if (! Result.second) {/// if the result does not exist association.swap(result.first->second); } // Auto refs_it = associations. Find (Overlap); // Auto refs_it = associations. 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); } } } } } // Call setHasAssociatedObjects outside the lock, since this // will call the object's _noteAssociatedObjects method if it // has one, and this may trigger +initialize which might do // arbitrary stuff, including setting more associated objects. if (isFirstAssociation) object->setHasAssociatedObjects(); // release the old value (outside of the lock). Release the old value associated association. ReleaseHeldValue (); }Copy the code

Through the source code we see the general process is:

  • Create an AssociationsManager management class

  • Get static hash table :associations

  • Check whether the associated value value is null

    • If empty, go:Insert a null valueProcess.
    • If not, proceed to the next step
  • With the try_emplace method, create an empty ObjectAssociationMap to fetch the queried key-value pair

  • If no key is found, insert an empty BucketT and return true

  • The setHasAssociatedObjects method is used to indicate that an object has an associated object

  • An ObjcAssociation is formed with the current policy and value to replace the BucketT value

  • Mark the ObjectAssociationMap as false for the first time

    The source code to debug

    With an overview of the process, let’s start debugging breakpoints

ifValue of the variable before (value)

With LLDB we print the values of Overlap, association, manager, Associations, and value

(lldb) p disguised (DisguisedPtr<objc_object>) $0 = (value = 18446744069393517536) (lldb) p association (objc::ObjcAssociation) $1 = {_policy = 3_value = 0x0000000100004080 "hahaha"} (LLDB) P Manager (objc::AssociationsManager) $2 = {} (lldb) p associations (objc::AssociationsHashMap) $3 = { Buckets = nil NumEntries = 0 NumTombstones = 0 NumBuckets = 0} (LLDB) p value (__NSCFConstantString *) $4 = 0x0000000100004080 "ha ha ha"Copy the code
valueNot an empty process

Above we see that value is not empty, so we go into the if statement to continue debugging.

  • p refs_result

    (lldb) p refs_result
    (std::pair<objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>, bool>) $5 = {
      first = {
        Ptr = 0x00000001012102a0
        End = 0x0000000101210300
      }
      second = true
    }
    Copy the code

    You can see that the data structure for refs_result looks complex, but the value is simple, with two properties first and second. The value of first is:

(lldb) p $5.first.Ptr
(objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>::pointer) $6 = 0x00000001012102a0
(lldb) p $5.first.End
(objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>::pointer) $7 = 0x0000000101210300
Copy the code

Second is true, so isFirstAssociation = true is executed.

  • try_emplaceMethod,associationsCall thetry_emplaceMethod, let’s take a look at its source code
template <typename... Ts> std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) { BucketT *TheBucket; If (LookupBucketFor(key, TheBucket)) /// return STD ::make_pair(makeIterator(TheBucket, getBucketsEnd(), true), false); // Already in map. // Otherwise, insert the new element. /// Return TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...) ; return std::make_pair( makeIterator(TheBucket, getBucketsEnd(), true), true); }Copy the code
  1. throughLookupBucketFormethodsFind the bucket, if the map alreadyThere are,Direct return, includingmake_pairThe second parameter ofBoolean value to false
  2. If you don’t haveHave foundbyInsertIntoBucketInsert map, wheremake_pairThe second parameter ofBoolean value to true

We break in to debug using LLDB

`p TheBucket

(lldb) p TheBucket
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > *) $1 = 0x0000000101c04200
(lldb) p *$1
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >) $2 = {
  std::__1::pair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > = {
    first = (value = 18446744069384153152)
    second = {
      Buckets = nil
      NumEntries = 0
      NumTombstones = 0
      NumBuckets = 0
    }
  }
}
Copy the code

See that the type of TheBucket is the same as the type of the attribute in refs_result.

  • LookupBucketFormethods

When we look into the LookupBucketFor source code, we find that there are two implementations. The difference between them is that FoundBucket parameter types first implement the multi-const modifier.

We debug the breakpoint and find that the second implementation is being called, and the second method calls the first implementation internally. Let’s look at the first implementation source code, with the process description in the comments.

template<typename LookupKeyT> bool LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const { const BucketT *BucketsPtr = getBuckets(); const unsigned NumBuckets = getNumBuckets(); if (NumBuckets == 0) { FoundBucket = nullptr; return false; } // FoundTombstone - Keep track of whether we find a tombstone while probing. const BucketT *FoundTombstone = nullptr; const KeyT EmptyKey = getEmptyKey(); const KeyT TombstoneKey = getTombstoneKey(); assert(! KeyInfoT::isEqual(Val, EmptyKey) && ! KeyInfoT::isEqual(Val, TombstoneKey) && "Empty/Tombstone value shouldn't be inserted into map!" ); Unsigned BucketNo = getHashValue(Val) & (NumBuckets-1); unsigned ProbeAmt = 1; While (true) {// const BucketT *ThisBucket = BucketsPtr + BucketNo; // Found Val's bucket? If (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) { FoundBucket = ThisBucket; return true; } // If we found an empty bucket, The key doesn't exist in the set. // Insert it and return the default value. // If it is an empty bucket, the key doesn't exist in the set. 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 this is a tombstone, remember it. If Val ends up not in the map, // We prefer to return it than something that would require more probing. // Ditto for zero values If (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&! FoundTombstone) FoundTombstone = ThisBucket; // Remember the first tombstone found. if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && ! FoundTombstone) FoundTombstone = ThisBucket; // Otherwise, it's a hash collision or a tombstone, continue quadratic // probing. if (ProbeAmt > NumBuckets) { FatalCorruptHashTables(BucketsPtr, NumBuckets); } BucketNo += ProbeAmt++; BucketNo &= (NumBuckets-1); }}Copy the code

The second implementation of LookupBucketFor

  • bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) { const BucketT *ConstFoundBucket; Bool Result = const_cast<const DenseMapBase *>(this) ->LookupBucketFor(Val, ConstFoundBucket); // Call the first LookupBucketFor method to find FoundBucket = const_cast<BucketT *>(ConstFoundBucket); // If it is found to copy to the second argument, because the second argument is a reference type, it will get the value directly where the call is made. The try_emplace method TheBucket return Result; }Copy the code
  • Continue the process with value true

    Let’s look at the value of refs before we execute the try_emplace method later

    (lldb) p refs
    (objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >) $3 = {
      Buckets = nil
      NumEntries = 0
      NumTombstones = 0
      NumBuckets = 0
    }
    Copy the code

    Value of refs after try_emplace method

    (lldb) p refs
    (objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >) $4 = {
      Buckets = 0x0000000100711390
      NumEntries = 1
      NumTombstones = 0
      NumBuckets = 4
    }
    Copy the code

    The first try_emplace execution inserts an empty bucket with no value. The second try_emplace execution inserts an empty bucket with an ObjectAssociationMap (value, policy). The second try_emplace execution inserts an empty bucket with an ObjectAssociationMap (value, policy).

    Second is true, and the value of the property is associated.

Associated object structure

The Settings of associated objects are as follows:

Attribute design hash table structure is as follows:

There are many associated object maps in the map, the type is ObjectAssociationMap, where the key is DisguisedPtr< objC_object >, for example, JSPerson will correspond to an ObjectAssociationMap, JSTeacher will also have an ObjectAssociationMap.

The ObjectAssociationMap hash table has a number of key-value pairs, where key is of type const void *, _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) Key is the string that we set when we associate the property, and value is of type ObjcAssociation

valueEmpty process

This process is actually the else process, which is the process where we set value to nil, which is basically removing the association.

else { 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); }}}}Copy the code
  • The iterator iterator query in AssociationsHashMap is found according to DisguisedPtr
  • Clean iterator
  • In fact, if you insert empty, you clear

Objc_getAssociatedObject process

inmainMethod to add a value
int main(int argc, const char * argv[]) { @autoreleasepool { JSPerson *person = [[JSPerson alloc] init]; Person.category_name = @" hahaha "; NSString *name = person.category_name; NSLog(@"done"); } return 0; }Copy the code
objc_getAssociatedObjectThe source code to achieve
id
objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}
Copy the code

Call the _object_get_associative_reference function.

_object_get_associative_referenceThe source code
id _object_get_associative_reference(id object, const void *key) { ObjcAssociation association{}; {/// create AssociationsManager AssociationsManager; /// Obtain the static hash AssociationsHashMap &associations(manager.get()); / / / / / find the iterator, i.e. to get buckets AssociationsHashMap: : iterator I = associations. The find ((objc_object *) object); if (i ! = associations.end()) {// If the iterator is not the last to continue fetching // The iterator that found the ObjectAssociationMap gets a value ObjectAssociationMap modified by the attribute modifier &refs = i->second; / / consult ObjectAssociationMap according to the key, that is, get the bucket ObjectAssociationMap: : iterator j = refs. Find (key); if (j ! = refs.end()) {// Get ObjcAssociation association = j->second; association.retainReturnedValue(); }}} / / / the return value return association. AutoreleaseReturnedValue (); }Copy the code

Look at the source code analysis is mainly divided into the following steps

  • Create an AssociationsManager management class

  • Get the static hash table: AssociationsHashMap

  • Using the find method DisguisedPtr, the iterator iterator query in AssociationsHashMap is found

  • If the iterator is not the last to continue fetching: ObjectAssociationMap (Policy and Value)

  • The iterator that finds ObjectAssociationMap with the find method gets a value that is modified by the attribute modifier

  • Returns the value

    To find the wayfind
      iterator find(const_arg_type_t<KeyT> Val) {     
        BucketT *TheBucket;    
        if (LookupBucketFor(Val, TheBucket))       
           return makeIterator(TheBucket, getBucketsEnd(), true);    
        return end();   
      }
    Copy the code
    Through the source code to see the value process

    We just break to the _object_get_associative_reference function

performp iandp i->second:

```
(lldb) p i 
(objc::DenseMapBase<objc::DenseMap<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > >, DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > >::iterator) $0 = {   Ptr = 0x0000000100631d60   End = 0x0000000100631d80 } (lldb) 
p i->second 
(objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >) $1 = {   Buckets = 0x0000000100631d80   NumEntries = 1   NumTombstones = 0   NumBuckets = 4 }
```
Copy the code

Let’s do find again, and before we call find, we print j, where value is nil.

(lldb) p j
(objc::DenseMapBase<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >::iterator) $2 = {
  Ptr = 0x00007ffeefbff400
  End = 0x00000001002e70db
}
(lldb) p j->second
(objc::ObjcAssociation) $3 = {
  _policy = 4294980472
  _value = nil
}
(lldb) 
Copy the code

After executing the find method and printing again, we find that value already has a value, that is, the associated object is fetched.

(lldb) p j
(objc::DenseMapBase<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >::iterator) $4 = {
  Ptr = 0x0000000100631d80
  End = 0x0000000100631de0
}
(lldb) p j->second
(objc::ObjcAssociation) $5 = {
  _policy = 3
  _value = 0x0000000100004080 "哈哈哈"
}
Copy the code

conclusion

This article explores extensions and associated objects, where the extensions of a class are compiled with the class as part of the class at compile time.

The associated object setting process is as follows:

  • Create an AssociationsManager management class

  • Get static hash table :associations

  • Check whether the associated value value is null

    • If empty, go:Insert a null valueProcess.
    • If not, proceed to the next step
  • With the try_emplace method, create an empty ObjectAssociationMap to fetch the queried key-value pair

  • If no key is found, insert an empty BucketT and return true

  • The setHasAssociatedObjects method is used to indicate that an object has an associated object

  • An ObjcAssociation is formed with the current policy and value to replace the BucketT value

  • Mark the ObjectAssociationMap as false for the first time

    The process of the value of the associated object is as follows:

    • To create aAssociationsManagerManagement class
    • Get a static hash table:AssociationsHashMap
    • throughfindMethods according to theDisguisedPtrfindAssociationsHashMapIn theiteratorIterating query
    • If the iterated query is not the last to continue fetching:ObjectAssociationMap (policy and the value)
    • throughfindMethods to findObjectAssociationMapThe iterated query of thevalue
    • returnvalue