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
foundextension
In the statementattribute
andmethods
The compiled andclass
In together, asclass
That is to sayProperties and methods in the extension
inCompile time
Is added to theThis class
In 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
throughlldb
printro
The 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 in
Compile time
It’s compiled with the class as part of the class - Class extensions are just
The statement
, depends on theThis class
The 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
- through
objc_setAssociatedObject
Method to set the value. - through
objc_getAssociatedObject
Method 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 property
strategy
, 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 value
Process. - If not, proceed to the next step
- If empty, go:
-
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
value
Not 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_emplace
Method,associations
Call thetry_emplace
Method, 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
- through
LookupBucketFor
methodsFind the bucket
, if the map alreadyThere are
,Direct return
, includingmake_pair
The second parameter ofBoolean value to false
- If you don’t have
Have found
byInsertIntoBucket
Insert map, wheremake_pair
The 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.
LookupBucketFor
methods
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
value
Empty 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
inmain
Method 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_getAssociatedObject
The 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_reference
The 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 way
find
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 i
andp 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 value
Process. - If not, proceed to the next step
- If empty, go:
-
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 a
AssociationsManager
Management class - Get a static hash table:
AssociationsHashMap
- through
find
Methods according to theDisguisedPtr
findAssociationsHashMap
In theiterator
Iterating query - If the iterated query is not the last to continue fetching:
ObjectAssociationMap (policy and the value)
- through
find
Methods to findObjectAssociationMap
The iterated query of thevalue
- return
value
- To create a