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:
- Exploring the underlying principles of iOS alloc
- Exploration of the underlying principles of iOS structure in vivo alignment
- The nature of the object explored by iOS underlying principles & the underlying implementation of ISA
- The underlying principles of the ISA-class (part 1)
- The underlying principles of the ISA class (middle)
- The underlying principles of isA-Class (part 2)
- Exploring the nature of Runtime Runtime & Methods in iOS Underlying principles
- Objc_msgSend explores the underlying principles of iOS
- Runtime Runtime slow lookup process
- Dynamic method resolution for exploring the underlying principles of iOS
- IOS underlying principles to explore the message forwarding process
- IOS Application loading principle
- Application loading principle (2)
- IOS underlying principle exploration class load
- 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 pass
runtime
Add attributes to the category - Classification using
@property
If you define variables, only variables will be generatedGetter and setter
Declaration 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