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)

Summary column for the above

  • Summary of the phase of iOS underlying principle exploration

preface

In the previous two articles, we explored application loading by linking the image file images to our application via dyld. However, only the mapping is done, and it is not loaded into memory. Let’s say we have a class that has methods, protocols, and so on, that needs to be loaded into memory so that we can instantiate the class and retrieve its methods.

We know that the linked image file is in machO format, so how do we read it into memory using the address in machO? It is better to have a table that can store all the class information, and then initialize the class, ro and RW of its internal data. I don’t know if this conjecture is correct, so let’s verify step by step to see how the class loading process is.

_objc_init

We called _dyLD_OBJC_notify_register inside _objc_init in the last chapter when we loaded our images so we’ll start with _objc_init, Explore the related init before _dyLD_OBJC_notify_register:

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * _objc_init boot initialization, Inform program * dyld registered our mirror before the library initialization time is called libSystem * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / void _objc_init(void) { static bool initialized = false; if (initialized) return; initialized = true; // Fix delayed initialization until a mirror using objc is found? // Initialize the environment variable environ_init(); // Thread local storage initialization tls_init(); // run the c++ static constructor. Libc calls _objc_init() before dyld calls the static constructor, so we'll have to do static_init() ourselves; / / initializes the whole table unattachedCategories, allocatedClasses runtime_init (); // Initialize libobJC exception_init(); #if __OBJC2__ cache_t::init(); #endif // Start the callback mechanism. Normally this doesn't do anything because all initialization is lazy, but for some processes, we can't wait to load trampolines dylib _imp_implementationWithBlock_init(); _dyld_objc_notify_register(&map_images, load_images, unmap_image); #if __OBJC2__ didCallDyldNotifyRegister = true; #endif }Copy the code

_dyld_objc_notify_register(&map_images, load_images, unmap_image)

  • Why in this functionmap_imagesI need to get the address,load_imagesDon’t need it?
  1. Let’s take the address right herePointer passedSo that the function here can change in sync with the function pointer called internally, because themap_imagesIt’s important, because it starts to map the entire image file, which is a time-consuming process, and if it doesn’t synchronize, then it gets confused, so keep the addresses synchronized;
  2. whileload_iamgesJust callloadMethods.

map_images

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * map_images * * dyld mapped to the given image processing In the use of specific to the abi calls after the lock code has nothing to do with the abi * * lock: write lock runtimeLock * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / void map_images(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[]) { mutex_locker_t lock(runtimeLock); return map_images_nolock(count, paths, mhdrs); }Copy the code

Very simple, two lines of code here we are map_images_nolock:

map_images_nolock

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * map_images_nolock * * dyld mapped to the given image processing All class registrations and fixes are performed (or delayed waiting) * find missing superclasses etc and call +load method * * Info[] in bottom-up order, That is, LibobJC will lock arrays earlier than any library linked to LibobJC * * :loadMethodLock(old) or runtimeLock(new) is fetched by map_images **********************************************************************/ #if __OBJC2__ #include "objc-file.h" #else #include "objc-file-old.h" #endif void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]) { static bool firstTime = YES; header_info *hList[mhCount]; uint32_t hCount; size_t selrefCount = 0; // Perform initial initialization if needed // This function is called before the normal library initializer // Fix delayed initialization until a mirror using objc is found? if (firstTime) { ... } if (PrintImages) { ... } // Find all mirrors with objective-C metadata hCount = 0; // Count classes. Size various table based on the total. int totalClasses = 0; int unoptimizedTotalClasses = 0; { uint32_t i = mhCount; while (i--) { ... }} // Execution must be delayed to a one-time run-time initialization // The executable itself was found. This needs to be done ahead of time // further initialization // The executable may not be in this infoList, if // the executable does not contain Objective-C code, but objective-C // dynamic load later if (firstTime) {... } if (hCount > 0) { _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses); } firstTime = NO; For (auto func: loadImageFuncs) {for (uint32_t I = 0; i < mhCount; i++) { func(mhdrs[i]); }}Copy the code

After initial initialization, start _read_images:

_read_images

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * _read_images of head performs the initial processing * * the link The list starting with headerList * * is called: map_images_NOLock * * Lock: runtimeLock obtained by map_images **********************************************************************/ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { header_info *hi; uint32_t hIndex; size_t count; size_t i; Class *resolvedFutureClasses = nil; size_t resolvedFutureClassCount = 0; static bool doneOnce; bool launchTime = NO; TimeLogger ts(PrintImageTimes); runtimeLock.assertLocked(); #define EACH_HEADER \ hIndex = 0; \ hIndex < hCount && (hi = hList[hIndex]); \ hIndex++ // 1, condition control to perform a load to find a table if (! doneOnce) { ... } // 2, fix @selector reference // sel name + address // one is the address in machO (with relative offset), One is that the addresses loaded by dyld (which need to be used as a benchmark) need to be fixed (redirected) by static size_t UnfixedSelectors; {... } ts.log("IMAGE TIMES: fix up selector references"); Bool hasDyldRoots = dyLD_shareD_cache_some_image_overridden (); for (EACH_HEADER) { ... } ts.log("IMAGE TIMES: discover classes"); // The list of classes and the list of non-lazy classes remain unmapped // The class reference and the super reference are remapped for message assignment. noClassesRemapped()) { ... } ts.log("IMAGE TIMES: remap classes"); #if SUPPORT_FIXUP // 5, fix the old objc_msgSend_fixup call site for (EACH_HEADER) {... } ts.log("IMAGE TIMES: fix up objc_msgSend_fixup"); For (EACH_HEADER) {... } ts.log("IMAGE TIMES: discover protocols"); For (EACH_HEADER) {for (EACH_HEADER) {for (EACH_HEADER) {... } ts.log("IMAGE TIMES: fix up @protocol references"); // 8. Find the category, do so only after the initial category For category / / found in startup is delayed until the first load_images call later / / calls to _dyld_objc_notify_register completed the if (didInitialAttachCategories) {... } ts.log("IMAGE TIMES: discover categories"); // 9. Category discovery must be late, To avoid potential races // When another thread calls the new class code before // This thread completes its fix // 10, + loading is handled by prepare_load_methods() // Implement a non-lazy class (for static instances implementing the +load method) for (EACH_HEADER) { ... } ts.log("IMAGE TIMES: realize non-lazy classes"); // implement the newly resolvedFutureClasses to prevent CF from manipulating them if (resolvedFutureClasses) {... } ts.log("IMAGE TIMES: realize future classes"); if (DebugNonFragileIvars) { realizeAllClasses(); } if (PrintPreopt) {... } #undef EACH_HEADER }Copy the code

At this point, it’s pretty obvious: map_images manages all symbols in files and dynamic libraries (class Protocol Selector category). Number 10 above is about class loading.

if (! doneOnce) { … } condition control to load once

// The marked pointer obfuscator is designed to make it more difficult for an attacker to construct a specific object as a marked pointer, memory in case of buffer overflow or other write control. When set, the obfuscator retries the payload value with the marked pointer XOR or. They start full of randomness used initializeTaggedPointerObfuscator (); Int namedClassesSize = (isPreoptimized()? Int namedClassesSize = (isPreoptimized()? unoptimizedTotalClasses : totalClasses) * 4 / 3; // This is an incorrect name :gdb_objc_realized_classes is actually named classes that are not in the dyld shared cache, implemented or not. This list excludes lazy named classes that must be found using the getClass hook gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); ts.log("IMAGE TIMES: first time tasks");Copy the code

for (EACH_HEADER) { … } Discover classes and fix unsolved future classes

Currently in the read_images section, the machO format is read from the image file into the table, and then restored to the class by the address, so how to restore to the class is the focus of the process, that is, the readClass section:

for (EACH_HEADER) { if (! MustReadClasses (hi, hasDyldRoots)) {// The image is fully optimized, Continue} classref_t const * classList = _getObjc2ClassList(hi, &count); bool headerIsBundle = hi->isBundle() bool headerIsPreoptimized = hi->hasPreoptimizedClasses(); for (i = 0; i < count; Class CLS = (Class) classList [I]; Class newCls = readClass(CLS, headerIsBundle, headerIsPreoptimized); if (newCls ! = CLS && newCls) {// The Class is moved but not deleted, this happens now // only if the new Class solves the future Class // don't be lazy to recognize the following Class resolvedFutureClasses = (Class *) realloc(resolvedFutureClasses, (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; }}}Copy the code

readClass

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * readClass * read the compiler writing classes and metaclass * return a pointer to the new class. This might be: * -cls * -nil (CLS lacks a weakly linked superclass) * -something else (this class's space is reserved for future classes) * * Note, All the work performed by this function is * mustReadClasses() performed by Preflightby. Do not change this function without updating that one * * Lock: runtimeLock obtained by map_images or objc_readClassPair **********************************************************************/ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) { const char *mangledName = cls->nonlazyMangledName(); If (0 == STRCMP (mangledName, mangledName)); "SMPerson")) {printf("\nSM our own added class needs to focus -- %s - %s", __func__, mangledName); } // nil (CLS missing a weak link superclass) if (missingWeakSuperclass(CLS)) {... } cls->fixupBackwardDeployingStableSwift(); Class replacing = nil; if (mangledName ! = nullptr) {if (Class newCls = popFutureNamedClass(mangledName)) {// The name was previously assigned to a future Class // copy objc_class to the structure of the Future Class If (newCls->isAnySwift()) {_objc_fatal("Can't complete future class request for '%s' ""because the real class is too big.", cls->nameForLogging()); } class_rw_t *rw = newCls->data(); const class_ro_t *old_ro = rw->ro(); memcpy(newCls, cls, sizeof(objc_class)); NewCls ->setSuperclass(CLS ->getSuperclass()); newCls->initIsa(cls->getIsa()); rw->set_ro((class_ro_t *)newCls->data()); newCls->setData(rw); freeIfMutable((char *)old_ro->getName()); free((void *)old_ro); addRemappedClass(cls, newCls); replacing = cls; cls = newCls; } } if (headerIsPreoptimized && ! (CLS == getClass(name)) {// Share the built-in list of classes in the cache // Fixme strict assertions don't work due to repetition // ASSERT(CLS == getClass(name)); ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName)); } else { if (mangledName) { //some Swift generic classes can lazily generate their names addNamedClass(cls, mangledName, replacing); } else { Class meta = cls->ISA(); const class_ro_t *metaRO = meta->bits.safe_ro(); ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass."); ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class."); } addClassTableEntry(cls); } / / for future reference: Shared cache never contain MH_BUNDLEs if (headerIsBundle) {CLS - > data () - > flags | = RO_FROM_BUNDLE; cls->ISA()->data()->flags |= RO_FROM_BUNDLE; } return cls; }Copy the code

ReadClass method in if (mangledName! = nullptr) {if (Class newCls = popFutureNamedClass(mangledName)) { Instead come after if (headerIsPreoptimized &&! Replacing) {} else from the process:

// Add name => CLS to named non-metaclass mapping; Warning against duplicate class names and keeping old mappings // Add the class name and address to the gdb_objc_realized_classes table addNamedClass(CLS, mangledName, replacing); // Add a class to the table of all classes. If addMeta is true; Automatically add class metaclass addClassTableEntry(CLS);Copy the code

However, there is no ro, RW data in the class we are concerned about, so its assignment is not implemented inside this method. But there are associated class names and class addresses.

Next, as we move down the _read_images flow, we find several more instances of class operations, following the breakpoint to realizeClassWithoutSwift:

realizeClassWithoutSwift – { ro rw superClass isa }

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * realizeClassWithoutSwift first initialize the * * the CLS for classes Includes allocating its read and write data * no Swift side initialization is performed * returns the actual class structure of the class * Lock :runtimeLock must be written locked by the caller **********************************************************************/ static Class realizeClassWithoutSwift(Class cls,  Class previously) { runtimeLock.assertLocked(); class_rw_t *rw; Class supercls; Class metacls; if (! cls) return nil; if (cls->isRealized()) { validateAlreadyRealizedClass(cls); return cls; } ASSERT(cls == remapClass(cls)); // Fix validation class not in closed part of shared cache? // Here's an example: You bought a house, now you just put your name on it, but the house is still empty, maybe you went to the furniture store and ordered the furniture, but it's not in the house yet. auto isMeta = ro->flags & RO_META; If (ro->flags & RO_FUTURE) {// This is a future course. Rw = CLS ->data(); ro = cls->data()->ro(); ASSERT(! isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); } else {// Normal class. Rw = objc::zalloc<class_rw_t>(); rw ::zalloc<class_rw_t>(); rw->set_ro(ro); rw->flags = RW_REALIZED|RW_REALIZING|isMeta; cls->setData(rw); } cls->cache.initializeToEmptyOrPreoptimizedInDisguise(); #if FAST_CACHE_META if (isMeta) cls->cache.setBit(FAST_CACHE_META); #endif // Select an index for this class // If no more indexes are available for the index, set CLS ->instancesRequireRawIsa CLS ->chooseClassArrayIndex(); if (PrintConnecting) { _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s", cls->nameForLogging(), isMeta ? " (meta)" : "", (void*)cls, ro, cls->classArrayIndex(), cls->isSwiftStable() ? "(swift)" : "", cls->isSwiftLegacy() ? "(pre-stable swift)" : ""); } // Implement superclasses and metaclasses, if they are not already implemented // For the root class, We need to do this after we've set rw_realize on it // This needs to be done after we've selected the class index for the root metaclass // This is assuming that none of these classes have Swift contents // or the Swift initializer has been called // Fix this, if we add support, Supercls = realizeClassWithoutSwift(remapClass(CLS ->getSuperclass()), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); #if SUPPORT_NONPOINTER_ISA if (isMeta) {// Metaclases do not require any features from non-pointer ISA // This allows a path to be provided for classes in objc_retain/objc_release cls->setInstancesRequireRawIsa(); } else {// Disable non-pointer for certain classes and/or platforms // set instancesRequirerawisa.bool instancesRequireRawIsa = CLS ->instancesRequireRawIsa(); bool rawIsaIsInherited = false; static bool hackedDispatch = false; If (DisableNonpointerIsa) {// Non-pointer is disabled for environment or application SDK version instancesRequireRawIsa = true; } else if (! HackedDispatch && 0 == STRCMP (ro->getName(), "OS_object")) {// libDispatch et al also act as vtable pointer hackedDispatch = true; instancesRequireRawIsa = true; } else if (supercls && supercls->getSuperclass() && supercls->instancesRequireRawIsa()) {else if (supercls && supercls->instancesRequireRawIsa()) InstancesRequireRawIsa = true; instancesRequireRawIsa = true; instancesRequireRawIsa = true; rawIsaIsInherited = true; } if (instancesRequireRawIsa) { cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited); }} // SUPPORT_NONPOINTER_ISA #endif // update the superclass and metaclass in case of remapping CLS ->setSuperclass(supercls); cls->initClassIsa(metacls); // Coordinate the instance variable offset/layout // this may reallocate class_ro_t, update the ro variable if (supercls &&! isMeta) reconcileInstanceVariables(cls, supercls, ro); CLS ->setInstanceSize(ro->instanceSize); // Copy some flags from ro to rw if (ro->flags & RO_HAS_CXX_STRUCTORS) { cls->setHasCxxDtor(); if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) { cls->setHasCxxCtor(); }} / / spread from ro or from the associated objects prohibited mark / / the superclass. The if ((ro - > flags & RO_FORBIDS_ASSOCIATED_OBJECTS) | | (supercls && supercls->forbidsAssociatedObjects())) { rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS; } // Connect this class to the list of subclasses of its parent class if (supercls) {addSubclass(supercls, CLS); } else { addRootClass(cls); } // Add class methodizeClass(CLS, previously); return cls; }Copy the code

methodizeClass(cls, previously)

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * methodizeClass repair method of CLS, protocol list and attribute list * Attach any particular category * lock: runtimeLock must by the caller to hold * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / static void methodizeClass(Class cls, Class previously) { runtimeLock.assertLocked(); bool isMeta = cls->isMetaClass(); auto rw = cls->data(); auto ro = rw->ro(); auto rwe = rw->ext(); const char *mangledName = cls->nonlazyMangledName(); If (PrintConnecting) {_objc_Inform ("CLASS: methodizing CLASS '%s' %s", CLS ->nameForLogging(), isMeta? "(meta)" : ""); } // Install the class itself to implement methods and attributes method_list_t *list = ro->baseMethods(); if (list) { prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr); // Rwe was not assigned a value. When was it assigned? if (rwe) rwe->methods.attachLists(&list, 1); } property_list_t *proplist = ro->baseProperties; if (rwe && proplist) { rwe->properties.attachLists(&proplist, 1); } protocol_list_t *protolist = ro->baseProtocols; if (rwe && protolist) { rwe->protocols.attachLists(&protolist, 1); } // If the root class has no additional method implementation, If (CLS ->isRootMetaclass()) {// the root metaclass addMethod(CLS, @selector(initialize), (IMP)&objc_noop_imp, "", NO); Additional categories if (previously) {} / / the if (isMeta) {objc: : unattachedCategories attachToClass (CLS, previously, ATTACH_METACLASS); } else {// When a class is relocated, class methods are used to classify it // can be registered in the course itself instead of // metaclass. Tell attachToClass look for these objc: : unattachedCategories attachToClass (CLS, previously, ATTACH_CLASS_AND_METACLASS); } } objc::unattachedCategories.attachToClass(cls, cls, isMeta ? ATTACH_METACLASS : ATTACH_CLASS); // SELs: check all SELs; For (const auto& meth: rw->methods()) {if (PrintConnecting) {_objC_Inform ("METHOD %c[%s %s]", isMeta? '+' : '-', cls->nameForLogging(), sel_getName(meth.name())); } ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name()); } #endif }Copy the code

prepareMethodLists

. For (int I = 0; int I = 0; int I = 0; int I = 0; i < addedCount; i++) { method_list_t *mlist = addedLists[i]; ASSERT(mlist); // Fix the selector if (! mlist->isFixedUp()) { fixupMethodList(mlist, methodsFromBundle, true/*sort*/); }}...Copy the code

fixupMethodList

static void fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort) { runtimeLock.assertLocked(); ASSERT(! mlist->isFixedUp()); // FixMe locks fewer attachMethodLists? // Dyld3 may have unique the list but not sorted if (! mlist->isUniqued()) { mutex_locker_t lock(selLock); For (auto& meth: *mlist) {const char *name = sel_cname(meth. Name ()); Meth. SetName (sel_registerNameNoLock(name, bundleCopy)); Stable_sort // Don't try to sort small lists, because they are immutable // Don't try to sort large lists of nonstandard size, such as stable_sort // Don't copy items correctly if (sort &&! mlist->isSmallList() && mlist->entsize() == method_t::bigSize) { method_t::SortBySELAddress sorter; std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter); } // Mark the list of methods as unique and sorted // Cannot mark small lists because they are immutable if (! mlist->isSmallList()) { mlist->setFixedUp(); }}Copy the code

The sorting by address part allows us to verify the before-and-after sorting of methods in our SMPerson class by adding code permission

Lazy loading

Classes that implement the +load method are non-lazily loaded. We comment out the + Load method in the SMPerson class and add the previous test code in the realizeClassWithoutSwift method

const char *mangledName = cls->nonlazyMangledName();
    if (0 == strcmp(mangledName, "SMPerson")) {
        printf("\nSM realizeClassWithoutSwift -- %s - %s", __func__ , mangledName);
    }
Copy the code

After the breakpoint is typed and the project is run, the general command bt looks at the stack information:

We see that the process is now behind the main() function, which is the data processing of the SMPerson class. In other words, lazy-loaded classes are loaded in a different order than non-lazy-loaded classes. Lazy-loaded classes delay the loading of class information until the first message is sent, i.e.

Class loading flowchart

So, the class loading process looks like this:

About the classification of

We have a SMPerson named SM category:

Clang-rewrite-objc main. m-o main-cate. CPP can be found after our classification, which is a structure of type _category_t:

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
	&_OBJC_$_CATEGORY_SMPerson_$_SM,
};
Copy the code

_category_t

Struct _category_t {const char *name; struct _class_t *cls; const struct _method_list_t *instance_methods; const struct _method_list_t *class_methods; const struct _protocol_list_t *protocols; const struct _prop_list_t *properties; };Copy the code

SMPerson (SM)

static struct _category_t _OBJC_$_CATEGORY_SMPerson_$_SM __attribute__ ((used, Section ("__DATA,__objc_const")) = {// static compilation does not run runtime, so, // &OBJC_CLASS_$_SMPerson (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_SMPerson_$_SM, (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_SMPerson_$_SM, (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_SMPerson_$_SM, (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_SMPerson_$_SM, }; static void OBJC_CATEGORY_SETUP_$_SMPerson_$_SM(void ) { _OBJC_$_CATEGORY_SMPerson_$_SM.cls = &OBJC_CLASS_$_SMPerson; }Copy the code

_method_list_t

static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_SMPerson_$_SM __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	2,
	{{(struct objc_selector *)"cate_instanceMethod2342134", "v16@0:8", (void *)_I_SMPerson_SM_cate_instanceMethod2342134},
	{(struct objc_selector *)"cate_instanceMethod6758675", "v16@0:8", (void *)_I_SMPerson_SM_cate_instanceMethod6758675}}
};

static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_SMPerson_$_SM __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"cate_classMethod2462457821", "v16@0:8", (void *)_C_SMPerson_SM_cate_classMethod2462457821}}
};

Copy the code

So, how is the classified data loaded and processed? CPP file we see that the classification information is stored in the _category_t structure, why can we call it directly from SMPerson? This is the focus of our next exploration. In the methodizeClass method above, there is an assignment to RWE

auto rwe = rw->ext();
Copy the code

So, that brings us to ext() :

class_rw_ext_t *ext() const { return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext); Class_rw_ext_t *extAllocIfNeeded() {auto v = get_ro_or_rwe(); if (fastpath(v.is<class_rw_ext_t *>())) { return v.get<class_rw_ext_t *>(&ro_or_rw_ext); } else { return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext)); }}Copy the code

A call to extAllocIfNeeded found by reading the source code is made in the following cases: AttachCategories, objC_class ::demangledName, class_setVersion, addMethods_finish, class_addProtocol, _class_addProperty, objc_duplicateClass, so write an article and let’s explore the class loading section about class loading. Everybody, come on!!

supplement

Runtime environment variables

Add Environment Variables: Project -> Scheme -> Edit Scheme

View the list: Open the terminal and run the following command to view the complete list

$ export OBJC_HELP=1
Copy the code