Write in the front: the exploration of the underlying principles of iOS is my usual development and learning in the continuous accumulation of a step forward road. I hope it will be helpful for readers to record my journey of discovery.Copy the code

The directory is as follows:

  1. Exploring the underlying principles of iOS alloc
  2. Exploration of the underlying principles of iOS structure in vivo alignment
  3. The nature of the object explored by iOS underlying principles & the underlying implementation of ISA
  4. The underlying principles of the ISA-class (part 1)
  5. The underlying principles of the ISA class (middle)
  6. The underlying principles of isA-Class (part 2)
  7. Exploring the nature of Runtime Runtime & Methods in iOS Underlying principles
  8. Objc_msgSend explores the underlying principles of iOS
  9. Runtime Runtime slow lookup process
  10. Dynamic method resolution for exploring the underlying principles of iOS
  11. IOS underlying principles to explore the message forwarding process
  12. IOS Application loading principle
  13. Application loading principle (2)
  14. IOS underlying principle exploration class load
  15. IOS Underlying principles to explore the classification of loading

Summary column for the above

  • Summary of the phase of iOS underlying principle exploration

preface

In the previous two articles, we looked at the overall process of class loading and classification loading, respectively. In fact, it explores a process from code to compilation to MachO to memory. In memory, all classes are mapped to a table, and mechod_list can then be called out. We’ve explored classes and classifications, so let’s explore class extensions today.

Classification of the category

  • Used specifically to add new methods to a class
  • You cannot add member variables and attributes to a class. If you add a member variable, you cannot retrieve it
  • Note: It can actually passruntimeAdd attributes to the category
  • Classification using@propertyIf you define variables, only variables will be generatedGetter and setterDeclaration of a method that does not generate the method’s implementation and underlined member variables

Class extend the extension

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

Extension low-level source exploration

Define an SMStudent class, its implementation, and its extension:

@interface SMStudent : NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) int age; - (void)study; - (void)askTeacher; @end @interface SMStudent () @property (nonatomic, copy) NSString *ext_name; @property (nonatomic, copy) NSString *ext_schoolName; - (void)ext_Study; - (void)ext_gotoSchool; @end @implementation SMStudent - (void)study { } - (void)askTeacher { } - (void)ext_Study { } - (void)ext_gotoSchool { }  @endCopy the code

Clang-rewrite-objc main.m -o main-ext. CPP

Then open the compiled main_ext.cpp file and search for _ext_name in the extension. We find what we want to see:

extern "C" unsigned long OBJC_IVAR_$_SMStudent$_name; extern "C" unsigned long OBJC_IVAR_$_SMStudent$_age; extern "C" unsigned long OBJC_IVAR_$_SMStudent$_ext_name; extern "C" unsigned long OBJC_IVAR_$_SMStudent$_ext_schoolName; struct SMStudent_IMPL { struct NSObject_IMPL NSObject_IVARS; int _age; NSString *_name; NSString *_ext_name; NSString *_ext_schoolName; }; . static NSString * _I_SMStudent_ext_name(SMStudent * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SMStudent$_ext_name)); } static void _I_SMStudent_setExt_name_(SMStudent * self, SEL _cmd, NSString *ext_name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SMStudent, _ext_name), (id)ext_name, 0, 1); }... static struct /*_method_list_t*/ { unsigned int entsize; // sizeof(struct _objc_method) unsigned int method_count; struct _objc_method method_list[20]; } _OBJC_$_INSTANCE_METHODS_SMStudent __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 20, {{(struct objc_selector *)"study", "v16@0:8", (void *)_I_SMStudent_study}, {(struct objc_selector *)"askTeacher", "v16@0:8", (void *)_I_SMStudent_askTeacher}, {(struct objc_selector *)"ext_Study", "v16@0:8", (void *)_I_SMStudent_ext_Study}, {(struct objc_selector *)"ext_gotoSchool", "v16@0:8", (void *)_I_SMStudent_ext_gotoSchool}, {(struct objc_selector *)"name", "@16@0:8", (void *)_I_SMStudent_name}, {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_SMStudent_setName_}, {(struct objc_selector *)"age", "i16@0:8", (void *)_I_SMStudent_age}, {(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_SMStudent_setAge_}, {(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_SMStudent_ext_name}, {(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_SMStudent_setExt_name_}, {(struct objc_selector *)"ext_schoolName", "@16@0:8", (void *)_I_SMStudent_ext_schoolName}, {(struct objc_selector *)"setExt_schoolName:", "v24@0:8@16", (void *)_I_SMStudent_setExt_schoolName_}, {(struct objc_selector *)"name", "@16@0:8", (void *)_I_SMStudent_name}, {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_SMStudent_setName_}, {(struct objc_selector *)"age", "i16@0:8", (void *)_I_SMStudent_age}, {(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_SMStudent_setAge_}, {(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_SMStudent_ext_name}, {(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_SMStudent_setExt_name_}, {(struct objc_selector *)"ext_schoolName", "@16@0:8", (void *)_I_SMStudent_ext_schoolName}, {(struct objc_selector *)"setExt_schoolName:", "v24@0:8@16", (void *)_I_SMStudent_setExt_schoolName_}} }; static struct /*_prop_list_t*/ { unsigned int entsize; // sizeof(struct _prop_t) unsigned int count_of_properties; struct _prop_t prop_list[2]; } _OBJC_$_PROP_LIST_SMStudent __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_prop_t), 2, {{"name","T@\"NSString\",C,N,V_name"}, {"age","Ti,N,V_age"}} };Copy the code

Obviously, a class extension is different from a class in that it defines properties that generate setters and getters that have been added to the class information. Let’s take a look at the process using objc source code. Let’s add a class extension to SMPerson to start exploring:

Note the stack information here:

Map_images -> map_images_nolock -> _read_images -> realizeClassWithoutSwift

conclusion

Attributes and methods in class extensions are compiled into the class information as part of the class at compile time. Classification, on the other hand, is more about dynamics.

associations

Since the class cannot add attributes, even if the call does not report an error, even if the build Success, will still crash at runtime, so we need to correlate the attributes in the class with the associated object. The code is as follows:

@interface SMPerson (Like)

@property (nonatomic, copy) NSString *like_name;
@property (nonatomic, assign) int like_time;

@end

@implementation SMPerson (Like)

- (void)setLike_name:(NSString *)like_name {
    
    objc_setAssociatedObject(self, "like_name", like_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)like_name {
    
    return objc_getAssociatedObject(self, "like_name");
}

@end
Copy the code

Next, let’s look at objc_setAssociatedObject:

objc_setAssociatedObject

void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}
Copy the code

_object_set_associative_reference

Void object_set_associative_reference(id object, const void *key, id value, uintPtr_t policy) {// This code works when object and key pass nil. Some code // presumably that's what keeps it from crashing. Examine and explicitly process it. // 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)); DisguisedPtr<objc_object> disguised{(objc_object *)object}; ObjcAssociation association{policy, value}; // Keep the new value (if any) outside the lock. association.acquireValue(); bool isFirstAssociation = false; { AssociationsManager manager; AssociationsHashMap &associations(manager.get()); if (value) { auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); If (refs_result.second) {/* this is the first association */ isFirstAssociation = true; } /* Create or replace association */ auto &refs = refs_result.first->second; auto result = refs.try_emplace(key, std::move(association)); if (! result.second) { association.swap(result.first->second); } } 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); / / erase refs. Erase (it); If (refs.size() == 0) {// erase(refs_it); }}}}} // call setHasAssociatedObjects outside the lock, because this will call the _noteAssociatedObjects method on the object. If there is one, this might trigger + initialization, what then? If (isFirstAssociation) object->setHasAssociatedObjects(); / / release the old value (lock) association. ReleaseHeldValue (); }Copy the code

forbidsAssociatedObjects

#define RW_FORBIDS_ASSOCIATED_OBJECTS (1<<20) bool forbidsAssociatedObjects() {return (data()->flags &  RW_FORBIDS_ASSOCIATED_OBJECTS); }Copy the code

AssociationsManager

class AssociationsManager { using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>; Static Storage _mapStorage; Public: / / lock to avoid repeating to create multithreaded AssociationsManager () {AssociationsManagerLock. The lock (); } ~AssociationsManager() { AssociationsManagerLock.unlock(); } AssociationsHashMap &get() { return _mapStorage.get(); } static void init() { _mapStorage.init(); }};Copy the code

AssociationsHashMap: AssociationsHashMap: AssociationsHashMap: AssociationsHashMap: AssociationsHashMap: AssociationsHashMap

AssociationsHashMap

try_emplace

// Insert the key-value pair into the map if the key is not already in the map // Construct the value in place if the key is not, otherwise // It does not move 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); TheBucket = InsertIntoBucket(TheBucket, Key, STD ::forward<Ts>(Args)...) ; return std::make_pair( makeIterator(TheBucket, getBucketsEnd(), true), true); }Copy the code

Let’s focus on a process for try_emplace:

/// LookupBucketFor -- Look for the appropriate bucket for Val and return it to /// FoundBucket. Return true if the bucket contains a key and a value, /// Returns false. 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 found a tombstone when 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; // Did you find Val's bucket? If so, return it if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {FoundBucket = ThisBucket; return true; } // If we find an empty bucket, If (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) { Then fill it in // the empty bucket we finally detected FoundBucket = FoundTombstone? FoundTombstone : ThisBucket; return false; } // If this is a tombstone, remember it. If Val doesn't appear on the map, we would rather return it than something that needs more probing // the same is true 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; If (ProbeAmt > NumBuckets) {FatalCorruptHashTables(BucketsPtr, NumBuckets); // If (ProbeAmt > NumBuckets) {FatalCorruptHashTables(BucketsPtr, NumBuckets); } BucketNo += ProbeAmt++; BucketNo &= (NumBuckets-1); } // Both methods have the same name, but with different arguments, LookupBucketFor template <typename LookupKeyT> bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) { const BucketT *ConstFoundBucket; bool Result = const_cast<const DenseMapBase *>(this) ->LookupBucketFor(Val, ConstFoundBucket); FoundBucket = const_cast<BucketT *>(ConstFoundBucket); return Result; }Copy the code

Gets the associated object value

objc_getAssociatedObject

id
objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}
Copy the code

_object_get_associative_reference

id _object_get_associative_reference(id object, const void *key) { ObjcAssociation association{}; { AssociationsManager manager; AssociationsHashMap &associations(manager.get()); / / the object to find ObjectAssociationMap AssociationsHashMap: : iterator I = associations. The find ((objc_object *) object); if (i ! = associations.end()) { ObjectAssociationMap &refs = i->second; / / find ObjcAssociation through key ObjectAssociationMap: : iterator j = refs. Find (key); if (j ! = refs.end()) { association = j->second; association.retainReturnedValue(); } } } return association.autoreleaseReturnedValue(); }Copy the code

Summary of Associated Objects

Through the above analysis, we can know the data structure relationship in the process of dealing with associated objects. There is a global data set AssociationHashMap, which stores all associated data internally. An object will correspond to an ObjectAssociationMap, and the attributes associated with the object, Are stored as key and value pairs in the ObjectAssociationMap.

Destruction of associated objects

The dealloc method is called when an object is destroyed and the associated object is released. rootDealloc->object_dispose->objc_destructInstance

objc_destructInstance

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * objc_destructInstance * in * without free memory destroy instance C++ calls destructor * calls ARC ivar cleanup * deletes associated references * returns obj. If 'obj' is nil, Do not do anything * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / void * objc_destructInstance obj (id) { 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

_object_remove_assocations

// This function is performance sensitive, Void object_remove_assocations(id object, id object, 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); Bool didReInsert = false; bool didReInsert = false; 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); } // SmallVector<ObjcAssociation *, 4> laterRefs; SmallVector<ObjcAssociation *, 4> laterRefs; 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

conclusion

Set the value process

  • 1: Create an AssociationsManager management class
  • 2: Obtain a unique global static hash Map
  • 3: Determine whether the inserted associated value exists:
  • 3.1: If yes, go to step 4
  • 3.2: Go if not exist: The associated object is inserted into an empty process
  • 4: create an empty ObjectAssociationMap to fetch the queried key-value pair
  • 5: If the key is missing, insert an empty BucketT and return
  • 6: The tag object has an associated object
  • 7: Replace BucketT with an ObjcAssociation consisting of the current decorating policy and value
  • ObjectAssociationMap = false for the first time

The associated object inserts an empty process

  • 1: Found the iterator iterator query in AssociationsHashMap according to DisguisedPtr
  • 2: cleans iterators
  • 3: In fact, if the insert vacant equivalent to clear

Value of the process

  • 1: Create an AssociationsManager management class
  • 2: Obtain a unique global static hash Map
  • 3: Found the iterator iterator query in AssociationsHashMap according to DisguisedPtr
  • 4: If the iterated query is not the last to get: ObjectAssociationMap (policy and value here)
  • 5: Find ObjectAssociationMap iterator to get a value modified by the attribute modifier
  • 6: return _value

Summary: It is a two-layer hash map, and the access time is two layers of processing (similar to two arrays).