preface

In the previous iOS Underlying Principles: Class Loading Principles (Classification), classification loading was analyzed. Today, class extension and associated objects will be explored.

The preparatory work

  • Objc4-818.2 – the source code

1. Class extension analysis

What is a class extension

  • This is theClass extensionsWe usually use is very much, it is also called the classification without name.

Low-level C++ analysis class extension

Add the following code to main.m:

@interface SSLPerson : NSObject
@property (nonatomic, copy) NSString *name;
- (void)say1;
@end

@interface SSLPerson ()
@property (nonatomic, copy) NSString *ext_name;
- (void)ext_say1;
@end

@implementation SSLPerson
+ (void)load {}
- (void)say1
{
    NSLog(@"SSLPerson-----say1");
}
- (void)ext_say1
{
    NSLog(@"SSLPerson-----ext_say1");
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {}   
    return 0;
}
Copy the code
  • Class extensionsShould be placedThe statementAfter that,implementationBefore, otherwise an error will be reported.

Open terminal, clang-rewrite-objc main.m -o main. CPP, and get main. CPP.

. struct SSLPerson_IMPL { struct NSObject_IMPL NSObject_IVARS; NSString *_name; NSString *_ext_name; }; static struct /*_method_list_t*/ { unsigned int entsize; // sizeof(struct _objc_method) unsigned int method_count; struct _objc_method method_list[10]; } _OBJC_$_INSTANCE_METHODS_SSLPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 10, {{(struct objc_selector *)"say1", "v16@0:8", (void *)_I_SSLPerson_say1}, {(struct objc_selector *)"ext_say1", "v16@0:8", (void *)_I_SSLPerson_ext_say1}, {(struct objc_selector *)"name", "@16@0:8", (void *)_I_SSLPerson_name}, {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_SSLPerson_setName_}, {(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_SSLPerson_ext_name}, {(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_SSLPerson_setExt_name_}, }; .Copy the code
  • As you can see,Class extensionsDid not likeclassificationSame thing, same thingcategory_tThat data structure.
  • A member variable for a class extension_ext_nameAnd the main class_nameIs put together, class extensionmethodsAnd the main classmethodsIs also put in the same list, so when are these methods added to the next analysis.

Class extension load analysis

For debugging purposes, recreate the following classes:

The realizeClassWithoutSwift function is called when the class is not lazily loaded, and we add a breakpoint to test it. Run the program to the breakpoint:

LLDB print:

  • As you can see,ext_say1,setName,ext_nameIf it already exists at this point, it means that the class extension was loaded with the content of the class, so to speak, as part of the class.

Class extension VS classification

1. What’s your name

  • Used specifically to add classesThe new method.
  • You cannot add a member attribute to a class. If you add a member variable, you cannot fetch it.
  • Note: It can actually passruntimeAdd attributes to the category.
  • Classification using@propertyIf you define variables, only variables will be generatedgetter,setterMethod declaration that cannot generate method implementations and underlined member variables.

Extension: Class extension

  • It’s kind of a special category, it’s also calledAnonymous classification.
  • You can add member variables to a class, but yesPrivate variables.
  • You can add methods to classes, againPrivate methods.
  • At compile time, and in the main classLoaded together.

2. Introduction of associated objects

We know that attributes in a category do not automatically generate member variables. This problem can be solved by associating objects.

Create SSLPerson+SSL1 category:

@interface SSLPerson (SSL1) @property (nonatomic, copy) NSString *cate_name; @end #import <objc/runtime.h> @implementation SSLPerson (SSL1) - (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"); } @endCopy the code

Click on objc_setAssociatedObject to go to the source code:

void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}
Copy the code
  • below_object_set_associative_referenceMake a detailed analysis.

Third, the underlying analysis of the associated object

_object_set_associative_reference

Enter the _object_set_associative_reference:

Void object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) { Key is an identity key, value is a value, and policy is a storage policy. DisguisedPtr< objC_Object > Premiere6.0 {(objc_Object *) Object}; ObjcAssociation Association {policy, value}; // retain the new value (if any) outside the lock. association.acquireValue(); // Key code {AssociationsManager manager; . } } template <typename T> class DisguisedPtr { uintptr_t value; static uintptr_t disguise(T* ptr) { return -(uintptr_t)ptr; } static T* undisguise(uintptr_t val) { return (T*)-val; } } class ObjcAssociation { uintptr_t _policy; id _value; . }Copy the code
  • DisguisedPtr<objc_object>willobjectIt’s packaged,ObjcAssociationwillpolicy,valueIt was packaged.
  • In the function ofAssociationsManagerThere are many articles on the Internet saying it isThe singletonIs it a singleton or not? Let’s analyze it.

AssociationsManager analysis

1. Constructor and destructor knowledge supplement

Create the SSLObjc structure and implement its constructor and destructor:

Struct SSLObjc {SSLObjc() {printf("SSL constructor call \n"); } ~SSLObjc() {printf("SSL destructor call \n"); }};Copy the code

Next breakpoint print debugging:

  • At this timeSSLObjc objc;Just got executed. We found outThe constructorI was called.

A step down from the break point:

  • At this point the scope ends,objcDestruction,The destructorIs also automatically called.

2. Source analysis of AssociationsManager

View AssociationsManager source code:

class AssociationsManager { using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>; static Storage _mapStorage; public: AssociationsManager() { AssociationsManagerLock.lock(); } ~AssociationsManager() { AssociationsManagerLock.unlock(); } AssociationsHashMap &get() { return _mapStorage.get(); } static void init() { _mapStorage.init(); }};Copy the code
  • You can seeAssociationsManagerNot a singleton, but through the constructorlock, the destructorunlock, to achieve thread-safe.
  • AssociationsManagerIt’s just for callingAssociationsHashMapThe just,AssociationsHashMapIs a singleton as it passes_mapStorage.get()To obtain,_mapStorageIs a global static variable that is unique wherever it is placed.

Associated object data structure diagram

Data structures associated with associated objects:

Source breakpoint debugging

Add code to main:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        SSLPerson *person = [SSLPerson alloc];
        person.cate_name = @"SSL";
    }   
    return 0;
}
Copy the code

Break to enter _object_set_associative_reference:

  • According to the print, you can seedisguised,association,associationsOf data structures, where_value = SSL, which proves that the current debugging is correct.

Going down:

  • printrefs_result“, found that the data structure is very long, in fact, the above so long is only the type, do not pay too much attention. The real numbers are in the next few lines, andrefs_result.secondThis piece of code is usedsecond.

Fourth, the underlying analysis set value process of the associated object

Run again to continue debugging breakpoints:

  • Next callassociations.try_emplaceThe function,associationsisAssociationsHashMapType.
  • The incomingdisguisedIt is aobjectAnd a new oneObjectAssociationMap, make it twoKey/value pairMake an association toassociationsIn the.

try_emplace

Break point to try_emplace:

  • BucketT *TheBucketTo create an emptyBucketT.
  • callLookupBucketFor(Key, TheBucket))And the incomingkeyisdisguised, andTheBucket.

LookupBucketFor

Enter the LookupBucketFor:

  • There are twoLookupBucketForFunction, which is passed noconsttheBucketT *&FoundBucketArgument, so call the following first.
  • The above is called from inside the functionLookupBucketFor.FoundBucketIs pointer passing, there are values that can be taken back, so the key code is in the above function.

Go to the LookupBucketFor function above:

template<typename LookupKeyT> bool LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const { const BucketT *BucketsPtr = getBuckets(); const unsigned NumBuckets = getNumBuckets(); // FoundTombstone - Keep track of whether we find a tombstone while probing. const BucketT *FoundTombstone = nullptr; const KeyT EmptyKey = getEmptyKey(); const KeyT TombstoneKey = getTombstoneKey(); // This is a hash function that computes the index unsigned BucketNo = getHashValue(Val) & (NumBuckets-1); // This is a hash function that computes the index unsigned BucketNo = getHashValue(Val) & (NumBuckets-1); unsigned ProbeAmt = 1; // If FoundBucket is found, assign to FoundBucket, return true // FoundBucket is not found, While (true) {const BucketT *ThisBucket = BucketsPtr + BucketNo; FoundBucket = FoundBucket; FoundBucket = FoundBucket; FoundBucket = FoundBucket; FoundBucket = FoundBucket; FoundBucket = FoundBucket; If (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {FoundBucket = ThisBucket; return true; } FoundBucket = FoundBucket; FoundBucket = FoundBucket; FoundBucket = FoundBucket; If (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {FoundBucket = FoundTombstone? FoundTombstone : ThisBucket; return false; } BucketNo += ProbeAmt++; BucketNo &= (NumBuckets-1); }}Copy the code
  • throughgetHashValue(Val) & (NumBuckets-1)Hash function, calculateBucketNoThe subscript.
  • whileLoop, find the location according to the subscript, and then assign the address toFoundBucketIn this case, we have an unassigned address, namelyisEqual(ThisBucket->getFirst(), EmptyKey), so returnfalse.

Return try_emplace

After the LookupBucketFor function is finished, return to try_emplace:

  • The returnedfalseSo I went down to the bottom.TheBucketIt is now stored inAssociationsHashMapI have a place in it, but I don’t have anything yet, so I’m going to insert something for it.
  • callInsertIntoBucketAnd the incomingTheBucketAnd the incomingkeyThat isdisguisedAnd the incomingArgsThat isObjectAssociationMap{}.

InsertIntoBucket

Enter the InsertIntoBucket:

template <typename KeyArg, typename... ValueArgs> BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key, ValueArgs &&... Values) {TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket); // Assign the Key to TheBucket in the first TheBucket->getFirst() = STD ::forward<KeyArg>(Key); // Assign ObjectAssociationMap{} to second ::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...) ; return TheBucket; }Copy the code

Enter the InsertIntoBucketImpl:

template <typename LookupKeyT> BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup, BucketT *TheBucket) { unsigned NewNumEntries = getNumEntries() + 1; unsigned NumBuckets = getNumBuckets(); If (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {this->grow(NumBuckets * 2); LookupBucketFor(Lookup, TheBucket); NumBuckets = getNumBuckets(); } else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <= NumBuckets/8)) { this->grow(NumBuckets); LookupBucketFor(Lookup, TheBucket); } if (KeyInfoT::isEqual(TheBucket->getFirst(), GetEmptyKey ())) {// Replacing an empty bucket. // Increase the number of incrementNumEntries(); } else if (KeyInfoT::isEqual(TheBucket->getFirst(), GetTombstoneKey ())) {// Replacing a tombstone. // Increase the number of incrementNumEntries(); decrementNumTombstones(); } else { // we should be purging a zero. No accounting changes. TheBucket->getSecond().~ValueT(); } return TheBucket; }Copy the code
  • When the quantity is greater than or equal toThree quarters ofWhen,2Doubled capacity, has been increasednumThe number of+ 1In the operation.

Const void *, association

Back to try_emplace:

  • You can seeTheBucketIt’s already worth it.
  • makeIteratorIn theiteratorisDenseMapIteratorType.
  • makeIterator(TheBucket, getBucketsEnd(), true)The last parameter of is fixedtrue.

Back to the _object_set_associative_reference function:

  • You can see it nowconst void *, objc::ObjcAssociationOf, belonging toObjectAssociationMapData structure.
  • At this timevalue,policyNot yet. It’s still thereassociationIn the water.

Continue debugging:

  • refs_result.firstWhat you get isbucket.bucket.secondWhat you get is what you created aboveObjectAssociationMapThat is to sayrefsIt was created aboveObjectAssociationMap.
  • And then call againtry_emplaceFunction, pass inkey(here,"cate_name"), andassociation. Store them as key-value pairs torefsthebucketMiddle, to these two levelsHash storedTo complete.

Summary of associated object setting process

  • To create aAssociationsManagerManagement class.
  • Gets a unique global static hashMap.
  • To determine whether the associated value for insertion exists:
    • Go there first4Step.
    • Go without: The associated object inserts an empty process.
  • Create an empty oneObjectAssociationMapTo get the key-value pair of the query.
  • And if it doesn’tkeyJust insert an empty oneBucketTGo in and return.
  • The tag object has an associated object.
  • With the current decorating policy and valueObjcAssociationReplace the originalBucketTIn the air.
  • Mark theObjectAssociationMapThe first time of isfalse.

Fifth, the underlying analysis of the associated object inserts the empty process

When value has no value, the code goes else:

Void object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) { Key is an identity key, value is a value, and policy is a storage policy. DisguisedPtr< objC_Object > Premiere6.0 {(objc_Object *) Object}; ObjcAssociation Association {policy, value}; // retain the new value (if any) outside the lock. association.acquireValue(); // Key code {AssociationsManager manager; AssociationsHashMap &associations(manager.get()); if (value) { ... } 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
  • inassociationsIn the table lookupdisguisedIf not found proceederaseClear the operation.

Vi. Process for the value of associated objects

  • To create aAssociationsManagerManagement class.
  • Gets a unique global static hashMap.
  • According to theDisguisedPtrfindAssociationsHashMapIn theiteratorIterating the query.
  • If the iterated query is not the last to retrieve:ObjectAssociationMap(There are strategies andvalue).
  • findObjectAssociationMapThe iterated query of thevalue.
  • return_value.

Release the associated object

When is the associated object released? The life cycle of the object determines the life cycle of the associated object. We take the release of the object as the breakthrough point for analysis.

Look at the source below:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { _object_set_associative_reference(object, key, value, policy); } void objc_removeAssociatedObjects(id object) { if (object && object->hasAssociatedObjects()) { _object_remove_assocations(object, /*deallocating*/false); }}Copy the code
  • We see that there areobjc_removeAssociatedObjectsFunction, you can do it the other way around, to see when the function was called.
  • It should be called when the object is released.

Search dealloc {, enter dealloc:

// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}
Copy the code

Enter the _objc_rootDealloc:

void
_objc_rootDealloc(id obj)
{
    ASSERT(obj);
    obj->rootDealloc();
}
Copy the code

Enter the rootDealloc:

inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; // fixme necessary? if (fastpath(isa.nonpointer && ! isa.weakly_referenced && ! isa.has_assoc && #if ISA_HAS_CXX_DTOR_BIT ! isa.has_cxx_dtor && #else ! isa.getClass(false)->hasCxxDtor() && #endif ! isa.has_sidetable_rc)) { assert(! sidetable_present()); free(this); } else { object_dispose((id)this); }}Copy the code

Enter the object_dispose:

id object_dispose(id obj) { if (! obj) return nil; objc_destructInstance(obj); free(obj); return nil; }Copy the code

Enter the objc_destructInstance:

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();
        
        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
        obj->clearDeallocating();
    }
    return obj;
}
Copy the code

Enter the _object_remove_assocations:

void _object_remove_assocations(id object, bool deallocating) { ObjectAssociationMap refs{}; { 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) { for (auto &ref: refs) { if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) { i->second.insert(ref); didReInsert = true; } } } if (! didReInsert) associations.erase(i); } } // Associations to be released after the normal ones. SmallVector<ObjcAssociation *, 4> laterRefs; // release everything (outside of the lock). for (auto &i: refs) { if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) { // If we are not deallocating, then RELEASE_LATER associations don't get released if (deallocating) laterRefs.append(&i.second); } else { i.second.releaseHeldValue(); } } for (auto *later: laterRefs) { later->releaseHeldValue(); }}Copy the code
  • To create aAssociationsManagerManagement class to get a unique global static hashMap, defined asassociations.
  • In order toobjectforkeyIn theassociationsRemove theiteratorIs defined asi.
  • After some judgment, yesassociations.erase(i);deletei.