IOS underlying principles + reverse article summary

In the previous iOS- Underlying Principle 16: DyLD associated with Objc article, we looked at how dyld is associated with objc. The main purpose of this article is to understand how information about classes is loaded into memory, focusing on map_images and load_images

  • Map_images: Manages all symbols in files and dynamic libraries, such as classes, protocols, selectors, and categories

  • Load_images: Load executes the load method

Among themcodethroughcompile, readMach-o executable fileAnd then read from Mach-omemory, as shown in the figure below

Map_images: loads the image file to the memory

Before looking at the source code, I first need to explain why map_images has & and load_images does not

  • map_imagesisReference typesThe world changes and so does it.
  • load_imagesisValue types, do not pass the value

Map_images Source process

The main purpose of the map_images method is to load the class information in Mach-O into memory

  • Enter themap_imagesThe source of
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
  • Enter themap_images_nolockSource code, its key code is_read_images
void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]) { //... // Find all images with Objective-C metadata. Find all images with Objective-C metadata hCount = 0; // Count classes. Size various table based on the total. Int totalClasses = 0; int unoptimizedTotalClasses = 0; // Code block: scope for local processing, that is, locally processing some events {//... Omit} / /... If (hCount > 0) {// load the image file _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses); } firstTime = NO; // Call image load funcs after everything is set up. When everything is set up, the image load function is called. for (auto func : loadImageFuncs) { for (uint32_t i = 0; i < mhCount; i++) { func(mhdrs[i]); }}}Copy the code

_read_images source implementation

_read_images is mainly to load class information, namely class, classification, protocol, etc., enter the source implementation of _read_images, mainly divided into the following parts:

  • 1, conditional control of a load
  • 2. Fix the @ Selector mess in precompile
  • Error messy class handling
  • 4. Fixed remapping of some classes that were not loaded by the image file
  • Fix some messages
  • 6, when there is a protocol in the class: readProtocol readProtocol
  • 7, fix protocol not loaded
  • 8, classification processing
  • 9, class loading processing
  • 10. For classes that have not been processed, optimize those that have been violated

1, conditional control of a load

In the doneOnce process, NXCreateMapTable is used to create a table to store the class information, that is, create a class hash table ‘ ‘gdb_objc_realized_classes. The purpose is to find the class easily and quickly

if (! doneOnce) { //... // Preoptimized classes don't go in this table. // 4/3 is NXMapTable's load factor int namedClassesSize = (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3; Gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); gdb_objC_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize) ts.log("IMAGE TIMES: first time tasks"); }Copy the code

Look at the comments in gdb_objc_realized_classes to show that this hash table is used to store named classes that are not in the shared cache, and has a capacity of four thirds of the number of classes, whether the class is implemented or not

// This is a misnomer: gdb_objc_realized_classes is actually a list of // named classes not in the dyld shared cache, //gdb_objc_realized_classes is actually a list of named classes that are not in the dyld shared cache, With or without implementing NXMapTable *gdb_objc_realized_classes; // exported for debuggers in objc-gdb.hCopy the code

2. Fix the @ Selector mess in precompile

This is done primarily by taking the static segment __objc_selrefs from Mach_O via _getObjc2SelectorRefs, traversing the list and calling sel_registerNameNoLock to add SEL to the hash of namedSelectors

Static size_t UnfixedSelectors; // Fix up @selector references to Fix @selector references //sel is not a simple string, but a string with an address; { mutex_locker_t lock(selLock); for (EACH_HEADER) { if (hi->hasPreoptimizedSelectors()) continue; bool isBundle = hi->isBundle(); __objc_selectorrefs SEL *sels = _getObjc2SelectorRefs(hi, &count); UnfixedSelectors += count; for (i = 0; i < count; I ++) {// sels ++) const char *name = sel_cname(sels[I]); Sel = sel_registerNameNoLock(name, isBundle); if (sels[i] ! = sels) {// If the SELS [I] address is not the same as that of the SELS [I], configure the SELS to be the same as that of the SELS [I]. }}}}Copy the code
  • Among them_getObjc2SelectorRefsTo obtain the static segment in Mach-o__objc_selrefs, the subsequent passage_getObjc2The initial Mach-o static segment fetch corresponds to a different section name
//      function name                 content type     section name
GETSECT(_getObjc2SelectorRefs,        SEL,             "__objc_selrefs"); 
GETSECT(_getObjc2MessageRefs,         message_ref_t,   "__objc_msgrefs"); 
GETSECT(_getObjc2ClassRefs,           Class,           "__objc_classrefs");
GETSECT(_getObjc2SuperRefs,           Class,           "__objc_superrefs");
GETSECT(_getObjc2ClassList,           classref_t const,      "__objc_classlist");
GETSECT(_getObjc2NonlazyClassList,    classref_t const,      "__objc_nlclslist");
GETSECT(_getObjc2CategoryList,        category_t * const,    "__objc_catlist");
GETSECT(_getObjc2CategoryList2,       category_t * const,    "__objc_catlist2");
GETSECT(_getObjc2NonlazyCategoryList, category_t * const,    "__objc_nlcatlist");
GETSECT(_getObjc2ProtocolList,        protocol_t * const,    "__objc_protolist");
GETSECT(_getObjc2ProtocolRefs,        protocol_t *,    "__objc_protorefs");
GETSECT(getLibobjcInitializers,       UnsignedInitializer, "__objc_init_func");
Copy the code
  • sel_registerNameNoLockThe source path is as follows:sel_registerNameNoLock -> __sel_registerNameAs shown below, the key code isauto it = namedSelectors.get().insert(name);, that is, insert selnamedSelectorsHash table
SEL sel_registerNameNoLock(const char *name, bool copy) { return __sel_registerName(name, 0, copy); // NO lock, maybe copy} 👇 static sel_registerName(const char *name, bool shouldLock, bool copy) { SEL result = 0; if (shouldLock) selLock.assertUnlocked(); else selLock.assertLocked(); if (! name) return (SEL)0; result = search_builtins(name); if (result) return result; conditional_mutex_locker_t lock(selLock, shouldLock); auto it = namedSelectors.get().insert(name); *it. First = (const char *)sel_alloc(name, copy); } return (SEL)*it.first; }Copy the code
  • Among themselector --> selIt’s not just a string, it’s a stringA string with an address

As shown below,sels[i]withselThe string is the same, but the address is not. Therefore, you need to adjust the value to be consistent. That is, fix up. The output can be displayed by printing debugging

Error messy class handling

Basically, all the classes are taken out of Mach-O and processed in the traversal

// Discover classes. Fix up unresolved future classes. Mark bundle classes. Bool hasDyldRoots = dyld_shared_cache_some_image_overridden(); ReadClass for (EACH_HEADER) {if (! mustReadClasses(hi, hasDyldRoots)) { // Image is sufficiently optimized that we need not call readClass() continue; } // Get all classes from the compiled classlist, that is, get the static segment __objc_classList from Mach-o, Classref_t const * classList = _getObjc2ClassList(hi, &count); bool headerIsBundle = hi->isBundle(); bool headerIsPreoptimized = hi->hasPreoptimizedClasses(); for (i = 0; i < count; i++) { Class cls = (Class)classlist[i]; Class newCls = readClass(CLS, headerIsBundle, headerIsPreoptimized); If (newCls!); // If (newCls!); // If (newCls!); = cls && newCls) { // Class was moved but not deleted. Currently this occurs // only when the new class resolved a ResolvedFutureClasses = (class *). // Non-lazily realize the class below. realloc(resolvedFutureClasses, (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; } } } ts.log("IMAGE TIMES: discover classes");Copy the code
  • Through the code debugging, know in the not executedreadClassMethods before,CLS is just an address

  • After execution,clsIs aThe name of the class

So at this point, the information for the class is currently stored only as the address + name

4. Fixed remapping of some classes that were not loaded by the image file

Remap unmapped classes and Super classes

  • The _getObjc2ClassRefs is a reference to the static section __objc_Classrefs in Mach-o

  • _getObjc2SuperRefs is a reference to the static segment __objc_superrefs in Mach-o

  • As you can see from the comments, the classes that are being used by remapClassRef are lazily loaded classes, so this part of the code is not executed when initially debugged

// 3, Restore a list of classes that were not loaded by the image file // (Elementary) : Restore a list of classes that remain unremapped. // Class refs and Super refs are remelementary for message Dispatching. Class reference and Super reference will be remapped for message distribution // Debugging does not execute the process in if // unmapped Class and Super Class will be remapped, the remap Class is lazy loading Class if (! noClassesRemapped()) { for (EACH_HEADER) { Class *classrefs = _getObjc2ClassRefs(hi, &count); // Mak-o static segment __objc_classrefs for (I = 0; i < count; i++) { remapClassRef(&classrefs[i]); } // fixme why doesn't test future1 catch the absence of this? classrefs = _getObjc2SuperRefs(hi, &count); // Static section in Mach_O __objc_superrefs for (I = 0; i < count; i++) { remapClassRef(&classrefs[i]); } } } ts.log("IMAGE TIMES: remap classes");Copy the code

Fix some messages

The static segment __objc_msgrefs of Mach-o is obtained through _getObjc2MessageRefs, and the function pointer is registered through fixupMessageRef, and fixed as the new function pointer

// Fix up old objc_msgSend_fixup call sites for (EACH_HEADER) {// _getObjc2MessageRefs Get the static segment __objc_msgrefs message_ref_t *refs = _getObjc2MessageRefs(hi, &count); if (count == 0) continue; if (PrintVtables) { _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch " "call sites in %s", count, hi->fname()); } // for (I = 0; I = 0; i < count; i++) { fixupMessageRef(refs+i); } } ts.log("IMAGE TIMES: fix up objc_msgSend_fixup"); #endifCopy the code

6, when there is a protocol in the class: readProtocol readProtocol

//6, "readProtocol" // Discover protocols. Fix protocol refs. Discover protocols. For (EACH_HEADER) {extern objc_class OBJC_CLASS_$_Protocol; Class CLS = (Class)&OBJC_CLASS_$_Protocol; ASSERT(cls); // Obtain the protocol hash table -- protocol_map NXMapTable *protocol_map = protocols(); bool isPreoptimized = hi->hasPreoptimizedProtocols(); // Skip reading protocols if this is an image from the shared cache // and we support roots // Note, after launch we do need to walk the protocol as the protocol // in the shared cache is marked with isCanonical() and that may not // be true if some non-shared cache binary was chosen as the canonical // definition if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) { if (PrintProtocols) { _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s", hi->fname()); } continue; } bool isBundle = hi->isBundle(); // Obtain the __objc_protolist protocol list in mach-o from _getObjc2ProtocolList, Protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count); for (i = 0; i < count; I ++) {readProtocol(protolist[I], CLS, protocol_map, isPreoptimized, isBundle); } } ts.log("IMAGE TIMES: discover protocols");Copy the code
  • throughNXMapTable *protocol_map = protocols();Create a protocol hash table whose name isprotocol_map
/***********************************************************************
* protocols
* Returns the protocol name => protocol map for protocols.
* Locking: runtimeLock must read- or write-locked by the caller
**********************************************************************/
static NXMapTable *protocols(void)
{
    static NXMapTable *protocol_map = nil;
    
    runtimeLock.assertLocked();

    INIT_ONCE_PTR(protocol_map, 
                  NXCreateMapTable(NXStrValueMapPrototype, 16), 
                  NXFreeMapTable(v) );

    return protocol_map;
}
Copy the code
  • through_getObjc2ProtocolListGet the static segment in Mach-o__objc_protolistProtocol list that is read from the compiler and initializedprotocol
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
Copy the code
  • Loop through the protocol list and passreadProtocolMethod to add the protocol to the protocol_MAP hash table
readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
Copy the code

7, fix protocol not loaded

_getObjc2ProtocolRefs (not the same thing as __objc_protolist in 6) and then iterates over the protocol that needs to be fixed. Use remapProtocolRef to compare the current protocol and the protocol list in the same memory address of the same protocol is the same, if different, replace

// Preoptimized images may have the right // answer already but we don't know for sure. for (EACH_HEADER) { // At launch time, we know preoptimized image refs are pointing at the // shared cache definition of a protocol. We can skip the check on // launch, but have to visit @protocol refs for shared cache images // loaded later. if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized()) continue; __objc_protorefs protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count); for (i = 0; i < count; // remapProtocolRef(&protolist[I]); // remapProtocolRef(&protolist[I]); // remapProtocolRef(&protolist[I]); }} ts.log("IMAGE TIMES: fix up @protocol References ");Copy the code

The remapProtocolRef source implementation is as follows

/*********************************************************************** * remapProtocolRef * Fix up a protocol ref, in case the protocol referenced has been reallocated. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ static size_t UnfixedProtocolReferences; static void remapProtocolRef(protocol_t **protoref) { runtimeLock.assertLocked(); Protocol_t * newPROto = remapProtocol((protocol_ref_t)*protoref); if (*protoref ! = newproto) {// replace *protoref = newproto if the current protocol is different from the same memory address protocol; UnfixedProtocolReferences++; }}Copy the code

8, classification processing

This mainly handles classes, which need to be executed after the classes have been initialized and the data loaded into the classes. For classes that appear at runtime, the discovery of classes is deferred until the first load_images call after the call to _dyLD_OBJC_NOTIFy_register completes

//8, category processing // Discover categories. Only do this after the initial category // Attachment has been done categories present at startup, // discovery is deferred until the first load_images call after // the call to _dyld_objc_notify_register completes. rdar://problem/53119145 if (didInitialAttachCategories) { for (EACH_HEADER) { load_categories_nolock(hi); } } ts.log("IMAGE TIMES: discover categories");Copy the code

9, class loading processing

It is mainly to realize the loading process of class and realize the non-lazy loading class

  • Obtain the mach-o static segment __objC_NLclslist non-lazily loaded class table from _getObjc2NonlazyClassList

  • AddClassTableEntry inserts non-lazily loaded classes into the class table, stores them in memory, and does not load additions if they have already been added, making sure the entire structure is added

  • RealizeClassWithoutSwift realizeClassWithoutSwift realizeClassWithoutSwift realizeClassWithoutSwift realizeClassWithoutSwift

// Realize non-lazy classes (for +load methods and static instances) RealizeClassWithoutSwift realizeClassWithoutSwift realizeClassWithoutSwift realizeClassWithoutSwift realizeClassWithoutSwift For the load method and the static instance variable for (EACH_HEADER) {// obtain the mach-o static segment __objC_NLclslist from _getObjc2NonlazyClassList, classref_t const *classlist = _getObjc2NonlazyClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); const char *mangledName = cls->mangledName(); const char *LGPersonName = "LGPerson"; if (strcmp(mangledName, LGPersonName) == 0) { auto kc_ro = (const class_ro_t *)cls->data(); Printf (" _getObjc2NonlazyClassList: this is I want to study % s \ n ", LGPersonName); } if (! cls) continue; addClassTableEntry(cls); // Insert table, but already inserted, If (CLS ->isSwiftStable()) {if (CLS ->swiftMetadataInitializer()) {_OBJC_FATAL ("Swift class %s with a metadata initializer " "is not allowed to be non-lazy", cls->nameForLogging()); } // fixme also disallow relocatable classes // We can't disallow all Swift classes because of // classes like Swift.__emptyarraystorage} // Implement the current class, because the previous readClass read only the address + name, the class data is not loaded // implement all non-lazily loaded classes (instantiate some information about the class object, For example rw) realizeClassWithoutSwift(CLS, nil); } } ts.log("IMAGE TIMES: realize non-lazy classes");Copy the code

10. For classes that have not been processed, optimize those that have been violated

The main thing is to implement classes that have not been handled, and optimize classes that have been violated

// Realize newly-resolved future classes, in case CF manipulates them if (resolvedFutureClasses) { for (i = 0; i < resolvedFutureClassCount; i++) { Class cls = resolvedFutureClasses[i]; if (cls->isSwiftStable()) { _objc_fatal("Swift class is not allowed to be future"); } // Implement class realizeClassWithoutSwift(CLS, nil); cls->setInstancesRequireRawIsaRecursively(false/*inherited*/); } free(resolvedFutureClasses); } ts.log("IMAGE TIMES: realize future classes"); If (DebugNonFragileIvars) {// Implement all realizeAllClasses(); }Copy the code

The two methods we need to focus on are readClass in 3 and realizeClassWithoutSwift in 9

ReadClass: reads the class

Before the method is called, CLS is just an address. After the method is executed, CLS is the name of the class. The source code implementation is as follows

/*********************************************************************** * readClass * Read a class and metaclass as written by a compiler. Returns the new class pointer. This could be: Returns a pointer to a new class, which might be:  * - cls * - nil (cls has a missing weak-linked superclass) * - something else (space for this class was reserved by a future class) * * Note that all work performed by this function is preflighted by * mustReadClasses(). Do not change this function without updating that one. * * Locking: runtimeLock acquired by map_images or objc_readClassPair **********************************************************************/ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) { const char *mangledName = cls->mangledName(); Const char *LGPersonName = "LGPerson"; const char *LGPersonName = "LGPerson"; if (strcmp(mangledName, LGPersonName) == 0) { auto kc_ro = (const class_ro_t *)cls->data(); - research - printf (" % s % s \ n ", __func__, mangledName); } // If there are weak-linked classes missing from the parent class, Return nil if (missingWeakSuperclass(CLS)) {// No superclass (probably weak-linked). // Disavow any knowledge of this subclass. if (PrintConnecting) { _objc_inform("CLASS: IGNORING class '%s' with " "missing weak-linked superclass", cls->nameForLogging()); } addRemappedClass(cls, nil); cls->superclass = nil; return nil; } cls->fixupBackwardDeployingStableSwift(); // popFutureNamedClass (popFutureNamedClass, popFutureNamedClass, popFutureNamedClass, popFutureNamedClass, popFutureNamedClass, popFutureNamedClass) Class Replacing = nil; Class Replacing = nil; if (Class newCls = popFutureNamedClass(mangledName)) { // This name was previously allocated as a future class. // Copy objc_class to future class's struct. // Preserve future's rw data block. 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(); class_rw_t *rw = newCls->data(); const class_ro_t *old_ro = rw->ro(); memcpy(newCls, cls, sizeof(objc_class)); rw->set_ro((class_ro_t *)newCls->data()); newCls->setData(rw); freeIfMutable((char *)old_ro->name); free((void *)old_ro); addRemappedClass(cls, newCls); replacing = cls; cls = newCls; } // Check whether the class is loaded into memory if (headerIsPreoptimized &&! replacing) { // class list built in shared cache // fixme strict assert doesn't work because of duplicates // ASSERT(cls  == getClass(name)); ASSERT(getClassExceptSomeSwift(mangledName)); } else { addNamedClass(cls, mangledName, replacing); // Load the shared cache class addClassTableEntry(CLS); // Insert the table from the mach-o file into memory} // Insert the table from the Mach-o file into memory} shared cache never contains MH_BUNDLEs if (headerIsBundle) { cls->data()->flags |= RO_FROM_BUNDLE; cls->ISA()->data()->flags |= RO_FROM_BUNDLE; } return cls; }Copy the code

Through the source code implementation, mainly divided into the following steps:

  • throughmangledNameGets the name of the class wheremangledNameThe source implementation of the method is as follows
const char *mangledName() { // fixme can't assert locks here ASSERT(this); If (isRealized () | | isFuture ()) {/ / the initial judgment in lookupImp have similar return data () - > ro () - > name; } else {return ((const class_ro_t *)data())->name; // Instead, get name from mach-o data}}Copy the code
  • Return nil if there is a missing weak-linked class from the parent of the current class

  • In normal cases, popFutureNamedClass will not be used to determine whether the class needs to be processed later, because this is specifically for the future of the class to be processed, and you can also debug through breakpoints, we know that it will not go into the if process, so we will not operate on ro, rw

    • Data is mach-O data, not in class memory

    • The assignment of ro is strongly translated from data in Mach-o

    • The ro in RW is copied from ro

  • Use addNamedClass to add the current class to the gDB_objC_realized_classes hash table that has been created to hold all classes

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * addNamedClass load sharing classes in the cache insert table * Adds name = > cls to the named non-meta class map. Add name=> CLS to named non-metaclasses mapping * Warns about duplicate class names and keeps the old mapping. * Locking: runtimeLock must be held by the caller **********************************************************************/ static void addNamedClass(Class cls, const char *name, Class replacing = nil) { runtimeLock.assertLocked(); Class old; if ((old = getClassExceptSomeSwift(name)) && old ! = replacing) { inform_duplicate(name, old, cls); // getMaybeUnrealizedNonMetaClass uses name lookups. // Classes not found by name lookup must be in the // secondary meta->nonmeta table. addNonMetaClass(cls); } else {// add to the gdb_objc_realized_classes hash table NXMapInsert(gdb_objc_realized_classes, name, CLS); } ASSERT(! (cls->data()->flags & RO_META)); // wrong: constructed classes are already realized when they get here // ASSERT(! cls->isRealized()); }Copy the code
  • throughaddClassTableEntryTo add the initialized class toallocatedClassesTable, this table is inIos-underlying principle 16: Dyld is associated with objCIt’s mentioned in the article. It’s in_objc_initIn theruntime_initcreatesallocatedClassestable
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * addClassTableEntry Add a class to the * Add a class table of all class to the table of all classes. If addMeta is true, * automatically adds the metaclass of the class as well. * Locking: runtimeLock must be held by the caller. **********************************************************************/ static void addClassTableEntry(Class cls, bool addMeta = true) { runtimeLock.assertLocked(); // This class is allowed to be a known class via the shared cache or via // data segments, but it is not allowed to be in the dynamic table already. auto &set = objc::allocatedClasses.get(); ASSERT(set.find(CLS) == set.end()); ASSERT(set.find(CLS) == set.end()); if (! isKnownClass(cls)) set.insert(cls); If (addMeta) // add to the allocatedClasses hash table addClassTableEntry(CLS ->ISA(), false); }Copy the code

If we want to be inreadClassIn the source code toLocate the custom class, you canCustom add if judgment

conclusion

So in summary, the main purpose of readClass is to read the mach-O class into memory, that is, insert it into the table, but the current class only has two information: the address and the name, and the Mach-O data data has not been read

RealizeClassWithoutSwift: Implementation class

The realizeClassWithoutSwift method has operations related to ro and rw. This method is mentioned in the slow lookup of the message flow. The method path is: Slowly find (lookUpImpOrForward) – realizeClassMaybeSwiftAndLeaveLocked – realizeClassMaybeSwiftMaybeRelock – RealizeClassWithoutSwift (implementation class)

The realizeClassWithoutSwift method implements the class and loads the data of the class into memory. It has the following operations:

  • [Step 1] ReaddataData and SettingsRo, rw
  • [Step 2] Recursive invocationrealizeClassWithoutSwiftperfectInheritance chain
  • Step 3: PassmethodizeClassMethods the class

Step 1: Read data

Read class data and convert it to RO, initialize rW and copy ro to RW

  • Ro stands for readOnly, or read-only. It defines Memory at compile time, contains information about class names, methods, protocols, and instance variables, and because it is read-only, it belongs to Clean Memory, which is Memory that does not change after loading

  • Rw stands for readWrite, which can be read and written. Because of its dynamic nature, you may add attributes, methods, and protocols to the class. Advw in the Objective-C Runtime – WWDC 2020-Videos – Apple Developer AdvW in the Objective-C Runtime – WWDC 2020-Videos – Apple Developer In fact, only 10% of the classes in RW actually change their methods, so there is RWE, the extra information of the class. For those classes that do need additional information, you can assign one of the RWE extension records and slide it into the class for its use. Where RW belongs to dirty memory, and dirty memory is the memory that changes while the process is running. The class structure becomes ditry memory once it is used, because the run writes new data to it, such as creating a new method cache and pointing to it from the class

// fixme verify class is not in an un-dlopened part of the shared cache? Auto ro = (const class_ro_t *) CLS ->data(); // read the bits attribute of the class structure, //ro -- clean memory, which was determined at compile time auto isMeta = ro->flags & RO_META; Allocated tokens: if (ro->flags & RO_FUTURE) {// This was a future class. rw data is already allocated. Ro = CLS ->data()->ro(); ASSERT(! isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); Rw = objc::zalloc<class_rw_t>();} else {// Normal class.allocate writeable class data.rw = objc::zalloc<class_rw_t>(); Zalloc -- rw rw->set_ro(ro); / / rw ro ro set to temp of rw - > flags = RW_REALIZED | RW_REALIZING | isMeta; cls->setData(rw); // assign data to CLS as rw}Copy the code

Step 2: Recursively call realizeClassWithoutSwift to complete the inheritance chain

A recursive call to realizeClassWithoutSwift completes the inheritance chain and sets the RW for the current class, parent class, and metaclass

  • A recursive call to realizeClassWithoutSwift sets the superclass and metaclass

  • Sets the ISA pointing of the superclass and metaclass

  • Through addSubclass and addRootClass to set the parent-child two-way list pointing relationship, that is, the parent class can be found in the subclass, the subclass can be found in the parent class

// Realize superclass and metaclass, if they aren't already. // This needs to be done after RW_REALIZED is set above, for root classes. // This needs to be done after class index is chosen, for root metaclasses. // This assumes that none of those classes have Swift contents, // or that Swift's initializers have already been called. // fixme that assumption will be wrong if we add support // For ObjC subclasses of Swift classes. -- // realizeClassWithoutSwift calls realizeClassWithoutSwift recursively to complete the inheritance chain and handle the current class's parent class and metaclass. The main purpose is to determine the inheritance chain (class inheritance chain, metaclass inheritance chain) // implement metaclass, parent class // When isa finds the root metaclass, the root metaclass ISA is pointing to its own, will not return nil, resulting in an infinite loop -- remapClass search for the class in the table, if the table already has the class, will return a null value; Supercls = realizeClassWithoutSwift(remapClass(CLS ->superclass), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); . // Update superclass and metaclass in case of remapping -- class isa bistatic list; // Update superclass and metaclass in case of remapping -- class isa bistatic list cls->superclass = supercls; cls->initClassIsa(metacls); . // Connect this class to its superclass's subclass lists // Connect this class to its superclass's subclass lists // Connect this class to its superclass's subclass lists if (supercls) { addSubclass(supercls, cls); } else { addRootClass(cls); }Copy the code

There’s a problem here, when you recursively call realizeClassWithoutSwift, after ISA finds the root metaclass, the root metaclass’s ISA is pointing to itself, and it doesn’t return nil, so you have the following recursive termination conditions, and the goal is to ensure that the class is only loaded once

  • inrealizeClassWithoutSwiftIn the
    • Returns nil if the class does not exist

    • If the class is already implemented, the CLS is returned directly

static Class realizeClassWithoutSwift(Class cls, Class previously) { runtimeLock.assertLocked(); // If the class does not exist, return nil if (! cls) return nil; If (CLS ->isRealized()) return CLS; if (CLS ->isRealized()) return CLS; ASSERT(cls == remapClass(cls)); . }Copy the code
  • inremapClassMethod, ifclsIf no, return it directlynil
/*********************************************************************** * remapClass * Returns the live class pointer for cls, which may be pointing to * a class struct that has been reallocated. * Returns nil if cls is ignored because of weak linking. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ static Class remapClass(Class cls) { runtimeLock.assertLocked(); if (! cls) return nil; // If CLS does not exist, return nil auto *map = remappedClasses(NO); if (! map) return cls; auto iterator = map->find(cls); if (iterator == map->end()) return cls; return std::get<1>(*iterator); }Copy the code

Step 3: methodizeClass

MethodizeClass reads the method list (including the methods in the class), attribute list, and protocol list from RO and assigns them to RW, and returns CLS

// Add ro data to rW methodizeClass(CLS, previously); // Add ro data to rW methodizeClass(CLS, previously); return cls;Copy the code

Breakpoint debugging realizeClassWithoutSwift

If we need to track a custom class, we also need to add custom logic to the realizeClassWithoutSwift method in step 9 of the _read_images method before the call, and to add custom logic to the realizeClassWithoutSwift method, mainly to facilitate debugging of the custom class

  • _read_imagesThe ninth step in the methodrealizeClassWithoutSwiftAdd custom logic before the call

  • realizeClassWithoutSwiftMethod to add custom logic

Next, start our breakpoint debugging

  • inLGPersonThe rewrite+loadfunction

  • Rerun the program, and there we are_read_imagestheStep 9The custom logical part in

  • inrealizeClassWithoutSwiftCall part with breakpoint, run and break

  • Continue to run the program, the breakpoint comesrealizeClassWithoutSwiftMethod of custom judgment in the code

  • Continue toauto ro =Break, continue, break — this part is basicallyRead the data

Look at ro, whereauto isMeta = ro->flags & RO_META; // Judge metaclasses

  • In the elserw->set_ro(ro);Break point, break, checkrwFor the time of,rwis0x0To view the RW, which includesrorwe

x/4gx clsThe red box is 0

  • Go ahead and seex/4gx clsIn this case, theta is still theta0x0

This is where we need to lookset_ro, its path is:set_roset_ro_or_rwe(findget_ro_or_rweIs throughro_or_rw_ext_tType fromro_or_rw_extGet) —ro_or_rw_ext_tIn thero From the source coderotheTo obtainThere are two main cases:Is there a runtime– ifWith the runtime, fromrw– conversely, if there is no runtime, fromroReads the

  • inif (supercls && ! isMeta)Add breakpoint, continue to run broken, this point of the breakpointclsIt’s the address, guessCLS may be metaclasses

Let’s verify: YesclstheIsa pointer addressTo verify that it’s the same address, this one existsrecursive(in theSupercls =, metacls =Partial recursion)

MethodizeClass: methodizeClass

Among them, methodizeClass source implementation is as follows, mainly divided into several parts:

  • Post a list of properties, a list of methods, a list of protocols, and so on to the RWE

  • Methods in additional categories (explained in the next article)

static void methodizeClass(Class cls, Class previously) { runtimeLock.assertLocked(); bool isMeta = cls->isMetaClass(); auto rw = cls->data(); Rw auto ro = rw->ro(); auto rwe = rw->ext(); . // Install methods and properties that the class implements itself. // Install methods and properties that the class implements itself. // Install methods and properties that the class implements itself method_list_t *list = ro->baseMethods(); // Get ro baseMethods if (list) {prepareMethodLists(CLS, &list, 1, YES, isBundleClass(CLS)); If (rwe) rwe->methods. AttachLists (&list, 1); } // Add property_list_t *proplist = ro->baseProperties; // add property_list_t *proplist = ro->baseProperties; if (rwe && proplist) { rwe->properties.attachLists(&proplist, 1); } // Add a protocol. Protocol_list_t *protolist = ro->baseProtocols; if (rwe && protolist) { rwe->protocols.attachLists(&protolist, 1); } // Root classes get bonus method implementations if they don't have // them already. These apply before category replacements. if (cls->isRootMetaclass()) { // root metaclass addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO); } / / Attach categories. / / join methods of classification the if (previously) {if (isMeta) {objc: : unattachedCategories attachToClass (CLS, previously, ATTACH_METACLASS); } else { // When a class relocates, categories with class methods // may be registered on the class itself rather than on // the metaclass. Tell attachToClass to look for those. objc::unattachedCategories.attachToClass(cls, previously, ATTACH_CLASS_AND_METACLASS); } } objc::unattachedCategories.attachToClass(cls, cls, isMeta ? ATTACH_METACLASS : ATTACH_CLASS); . }Copy the code

The logic of rwe

The logic for adding rWE to the method list is as follows:

  • Obtain the base values of ro

  • Sort by the prepareMethodLists method

  • The RWE is processed by attachLists

How to sort methods

In the message flow slow search process ios-underlying principle 13: Message flow analysis of the slow search article, the method of the search algorithm is through the binary search algorithm, that SEL IMP is sorted, then how to sort it?

  • Enter theprepareMethodListsThe source code implementation, its internal is throughfixupMethodListMethods the sorting
static void prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount, bool baseMethods, bool methodsFromBundle) { ... // Add method lists to array. // Reallocate un-fixed method lists. // The new methods are PREPENDED to the method list array. for (int i = 0; i < addedCount; i++) { method_list_t *mlist = addedLists[i]; ASSERT(mlist); // Fixup selectors if necessary if (! mlist->isFixedUp()) { fixupMethodList(mlist, methodsFromBundle, true/*sort*/); }}... }Copy the code
  • Enter thefixupMethodListSource code implementation, is based onselector addressThe sorting
static void fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort) { runtimeLock.assertLocked(); ASSERT(! mlist->isFixedUp()); // fixme lock less in attachMethodLists ? // dyld3 may have already uniqued, but not sorted, the list if (! mlist->isUniqued()) { mutex_locker_t lock(selLock); // Unique selectors in list. for (auto& meth : *mlist) { const char *name = sel_cname(meth.name); meth.name = sel_registerNameNoLock(name, bundleCopy); } } // Sort by selector address. If (sort) {method_t::SortBySELAddress sorter; std::stable_sort(mlist->begin(), mlist->end(), sorter); } // Mark method list as uniqued and sorted mlist->setFixedUp(); }Copy the code

Validation method sorting

Next we can verify the sorting of methods by debugging

  • inmethodizeClassMethod to add custom logic and stop

  • readroIn themethodlist
    • p kc_ro
    • p $0->baseMethodList(auto kc_ro = kc_rw->ro(); — ro() — class_ro_t type view properties)
    • p *$1

– p
2. g e t ( 0 ) p 2.get(0) – p
2.get(1) – p
2. g e t ( 2 ) p 2.get(2) – p
2.get(3) …

  • Enter theprepareMethodListsMethod, put ro inbaseMethodssorting

  • Enter theprepareMethodListsSource code, add custom breakpoints (mainly for research purposes), execute breakpoints, run to custom logic and break (here addkc_isMeta, mainly for filtering outMethods in metaclasses of the same name)

  • Step by step, come tofixupMethodList, i.e.,Sequence of sel

  • Enter thefixupMethodListSource code implementation, (sel according to selAdress sort), again break point, to the following part, that is, the method through a layer of sort

– p mlist – p *
7 p 7 – p
8. The get (0), p
8. g e t ( 1 ) , p 8. The get (1), p
8. Get (2), p $8. Get (3)

Methodlist: MethodList: methodList: methodList: methodListmethodizeClassMethod in the implementation classmethodsOf (an agreement, etcserialization

AttachToClass method

The main method in methodList is to add the classification to the main class. The source code implementation is as follows

void attachToClass(Class cls, Class previously, int flags) { runtimeLock.assertLocked(); ASSERT((flags & ATTACH_CLASS) || (flags & ATTACH_METACLASS) || (flags & ATTACH_CLASS_AND_METACLASS)); const char *mangledName = cls->mangledName(); const char *LGPersonName = "LGPerson"; if (strcmp(mangledName, LGPersonName) == 0) { bool kc_isMeta = cls->isMetaClass(); auto kc_rw = cls->data(); auto kc_ro = kc_rw->ro(); if (! Kc_isMeta) {printf("%s: this is what I want to study %s \n",__func__,LGPersonName); } } auto &map = get(); auto it = map.find(previously); // If (it! = map.end()) {// This will come in: when the main class does not implement load, the class will start loading, forcing the main class to load, which will go into the if process category_list &list = it->second; If (flags & ATTACH_CLASS_AND_METACLASS) {// determine whether the metaclass is ATTACH_CLASS_AND_METACLASS; attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS); / / instance methods attachCategories (CLS - > ISA (), the list. The array (), the list. The count (), otherFlags | ATTACH_METACLASS); // Class method} else {attachCategories(CLS, list.array(), list.count(), flags); } map.erase(it); }}Copy the code

Because the outer loop in attachToClass is to find a category and then it goes into attachCategories once, so it goes through one

AttachCategories method

The classified data is prepared in the attachCategories method, which is implemented as follows in the source code

static void attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count, int flags) { if (slowpath(PrintReplacedMethods)) { printReplacements(cls, cats_list, cats_count); } if (slowpath(PrintConnecting)) { _objc_inform("CLASS: attaching %d categories to%s class '%s'%s", cats_count, (flags & ATTACH_EXISTING) ? " existing" : "", cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : ""); } /* * Only a few classes have more than 64 categories during launch. * This uses a little stack, and avoids malloc. * * Categories must be added in the proper order, which is back * to front. To do that with the chunking, we iterate cats_list * from front to back, build up the local buffers backwards, * and call attachLists on the chunks. attachLists prepends the * lists, so the final result is in the expected order. */ constexpr uint32_t ATTACH_BUFSIZ = 64; method_list_t *mlists[ATTACH_BUFSIZ]; property_list_t *proplists[ATTACH_BUFSIZ]; protocol_list_t *protolists[ATTACH_BUFSIZ]; uint32_t mcount = 0; uint32_t propcount = 0; uint32_t protocount = 0; bool fromBundle = NO; bool isMeta = (flags & ATTACH_METACLASS); /* create rwe, so why do 'rwe' init here? Because we now have one thing to do: 'add attributes, methods, protocols, etc.' to 'this class' */ auto rwe = CLS ->data()->extAllocIfNeeded(); For (uint32_t I = 0; // Uint32_t I = 0; i < cats_count; i++) { auto& entry = cats_list[i]; method_list_t *mlist = entry.cat->methodsForMeta(isMeta); If (mlist) {if (McOunt == ATTACH_BUFSIZ) {ATTACH_BUFSIZ= 64, PrepareMethodLists (CLS, mLists, McOunt, NO, fromBundle); Rwe ->methods. AttachLists (mlists, McOunt); mcount = 0; } mlists[ATTACH_BUFSIZ - ++mcount] = mlist; fromBundle |= entry.hi->isBundle(); } property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi); if (proplist) { if (propcount == ATTACH_BUFSIZ) { rwe->properties.attachLists(proplists, propcount); propcount = 0; } proplists[ATTACH_BUFSIZ - ++propcount] = proplist; } protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta); if (protolist) { if (protocount == ATTACH_BUFSIZ) { rwe->protocols.attachLists(protolists, protocount); protocount = 0; } protolists[ATTACH_BUFSIZ - ++protocount] = protolist; } } if (mcount > 0) { prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle); AttachLists (mlists + attach_bufsiz-mcount, McOunt); //mlists + ATTACH_BUFSIZ - McOunt flushCaches(CLS) if (flags & ATTACH_EXISTING) flushCaches(CLS); } rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount); rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount); }Copy the code
  • inauto rwe = cls->data()->extAllocIfNeeded();It’s rWE creation, so why do it hereInitialization of RWE?? Because we’re going to do one thing right now: goThis classIn theAdd properties, methods, and protocolsAnd so on, that is, the original clean memory to be processed
    • Enter the source code of extAllocIfNeeded method to determine whether RWE exists, if there is direct access, if there is no open up

    • Enter extAlloc source code implementation, that is, the process of RWE 0-1, in this process, will be loaded in the class data data

class_rw_ext_t *extAllocIfNeeded() { auto v = get_ro_or_rwe(); If (fastpath(v.i <class_rw_ext_t *>())) {return v.get<class_rw_ext_t *>(); } else {return extAlloc(v.gate <const class_ro_t *>());} else {return extAlloc(v.gate <const class_ro_t *>()); }} 👇//extAlloc source code class_rw_ext_t * class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy) { runtimeLock.assertLocked(); Auto rwe = objc::zalloc<class_rw_ext_t>(); auto rwe = objc::zalloc<class_rw_ext_t>(); Rwe ->version = (ro->flags & RO_META)? 7:0; method_list_t *list = ro->baseMethods(); if (list) { if (deepCopy) list = list->duplicate(); rwe->methods.attachLists(&list, 1); } // See comments in objc_duplicateClass // property lists and protocol lists historically // have not been deep-copied // // This is probably wrong and ought to be fixed some day property_list_t *proplist = ro->baseProperties; if (proplist) { rwe->properties.attachLists(&proplist, 1); } protocol_list_t *protolist = ro->baseProtocols; if (protolist) { rwe->protocols.attachLists(&protolist, 1); } set_ro_or_rwe(rwe, ro); return rwe; }Copy the code
  • Rwe ->methods. AttachLists (mlists + attach_bufsiz-mcount, McOunt); That is stored at the end of mLists, the source of mlists data in front of the for loop

  • On debug run time, I found that the name in Category_t was LGPerson at compile time (see clang compile time so) and LGA at run time, which is the name of the category

  • Mlists [ATTACH_BUFSIZ – ++ McOunt] = mlist; After debugging, it is found that the McOunt at this time is equal to 1, that is, it can be understood as reverse insertion,64 is allowed to hold 64 (maximum 64 categories)

Conclusion: Attributes and methods need to be added to this class, so RWE needs to be initialized. Rwe initialization mainly involves: class, addMethod, addProperty, addProtocol. That is, rWE will be initialized only when the original class is modified or processed

AttachLists method: Insert

  • Among themMethods, attributesInheritance inentsize_list_tt.agreementIt is similar toentsize_list_ttImplementation, both2 d array
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> struct protocol_list_t { // count is pointer-sized by accident. uintptr_t count; protocol_ref_t list[0]; // variable-size size_t byteSize() const { return sizeof(*this) + count*sizeof(list[0]); } protocol_list_t *duplicate() const { return (protocol_list_t *)memdup(this, this->byteSize()); }... }Copy the code
  • Enter theattachListsMethod source implementation
void attachLists(List* const * addedLists, uint32_t addedCount) { if (addedCount == 0) return; If (hasArray()) {// many lists -> many lists // Calculate the size of old lists in the array uint32_t oldCount = array()->count; // Calculate the new size = old data size + new data size uint32_t newCount = oldCount + addedCount; SetArray ((array_t *)realloc(array(), array_t::byteSize(newCount))); Array ()->count = newCount; // The old data starts with the addedCount array subscript and stores the old lists, Memmove (array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0])); // New data is stored from the beginning of the array, and new lists are stored. Memcpy (array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); } else if (! list && addedCount == 1) { // 0 lists -> 1 list list = addedLists[0]; / / to add a list to mlists the first element of the list is a one-dimensional array} else {/ list / 1 - > many lists have a list, have to add a lot of list / / new list is classified, thinking from LRU algorithm, List* oldList = list; uint32_t oldCount = oldList ? 1:0; // Uint32_t newCount = oldCount + addedCount; // Uint32_t newCount = oldCount + addedCount; SetArray ((array_t *)malloc(array_t::byteSize(newCount))); Array ()->count = newCount; If (oldList) array()->lists[addedCount] = oldList; if (oldList) array()->lists[addedCount] = oldList; // memcpy (start position, what to put, how big to put) is a memory shift, Memcpy (array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); }}Copy the code

From the source code, we can know that the insert table is mainly divided into three cases:

  • [Case 1: many-to-many] If the current call to attachLists contains multiple one-dimensional arrays in the LIST_ARRAY_TT two-dimensional array

    • Calculate the size of the old lists in the array

    • Calculate the new capacity size = old data size + new data size

    • Based on the new size, create an array of type array_t, which is obtained through array()

    • Set array size

    • The old data starts from the addedCount array subscript to store the old lists. The size is the size of the old data * the size of a single old list, that is, the whole segment is shifted, which can be simply understood as the original data moved to the back, that is, the pointer offset

    • The new data is stored from the first position of the array, and the new lists are stored. The size is the size of the new data * the size of a single list. It can be simply understood that the later the data is added, the more in front, the more in front, and the call will be called first

  • [case 2:0 to 1] if the list_array_tt two-dimensional array called attachLists is empty and the number of new sizes is 1

    • Direct assignmentaddedListtheThe first list
  • [Case 3: one-to-many] If the list_ARRAY_TT two-dimensional array currently calling attachLists has only one one-dimensional array

    • Get the old list

    • Calculate the capacity and = Number of old lists + number of new lists

    • Create a collection of capacity and size of type array_t, that is, create an array, put it in array, and get it through array()

    • Sets the size of the array

    • To see if old exists, which it certainly does, put the old list at the end of the array

    • Memcpy (start position, what to put, how big to put) is a memory shift that stores a new list from the beginning of the array, where array()->lists indicates the first element position

In case 3, lists here refer to categories

  • This is the daily development, why subclass implementation of the parent class method will override the parent class method reason

  • Similarly, for methods of the same name, the classification method overrides the class method

  • This operation comes from the algorithmic idea that LRU is the least recently used, and the purpose of adding this newList is to use the methods in this newList, which is more valuable to the user, which is called first

  • The main reason why it’s going to be 1 to many is because of the addition of categories, that is, the old elements are in the back, the new elements are in the front, and the fundamental reason is to call category first, which is the point of classification

The difference between memmove and memcpy

  • When the memory size to be shifted is unknown, memmove is required to shift the memory to ensure safety

  • Memcpy copies several bytes from the start of the original memory address to the target memory address, which is fast

Rwe data is loaded

Rwe – data loading for this class

The following is a debugging procedure to verify the RWE data 0-1 by adding the method list of the class

  • inattachCategories -> extAllocIfNeeded -> extAllocAdd custom logic, run, and stop, as seen from the stack informationattachCategoriesIn the methodauto rwe = cls->data()->extAllocIfNeeded();Over here. What this does isOpen up rwe.
    • So why do it hereInitialization of RWE? Because we’re going to do one thing right now: goThis classIn theAdd properties, methods, and protocolsAnd so on, namely the originalclean memoryWe’re going to do it
    • rweIs in theClassification process, that is, RWE initialization. The following methods will involve RWE initialization:Category + addMethod + addPro + addProtocol

p rwep *$0For the time of,rweIn thelist_array_ttIs empty

  • If (list) {stop

    • p list
    • p *$2For the time of,listisLGPersonA list of methods for this class

  • Set a breakpoint at if (hasArray()) {in the attachLists method and run the breakpoint. Continuing the execution will take you to the else-if process, which is 0 to 1 — adding methods to the LGPerson class will take you to 0 to 1

p addedListsThis is the address of a list pointermlistsThe first element of themethod_list_t *const * p addedLists[0]p *$5 p addedLists[1]p *$7, will also have a value, mainly because memory is contiguously accessed by someone else

Conclusion: So 0 to 1 is a one-dimensional assignment, and the function path is: map_images -> _read_images -> readClass -> realizeClassWithoutSwift -> methodizeClass -> prepareMethodLists -> fixupMethodList -> attachToClass -> load_categories_nolock -> attachCategories -> extAllocIfNeeded -> extAlloc -> attachLists

Rwe — LGA classification data loading

  • Go ahead and print the list

    • p listIn this case, the list ismethod_list_tstructure

  • Method_list_t *mlist = entry.cat->methodsForMeta(isMeta); .

    • p mlist
    • P *$10, where mlist is classified LGA

  • If (McOunt > 0) {if (McOunt > 0) {if (McOunt > 0)

  • Let’s go one step furthermlistsSet of sets

  • In the command, mlists + attach_bufsiz-mcount indicates memory translation

    • p mlists + ATTACH_BUFSIZ - mcountBecause McOunt = 1, ATTACH_BUFSIZ = 64, we move from the first to 63, the last element
    • p *$14

    • p *$15 ,mlistsThe class tolerance of the last element isA list of methods for this class

  • Go to the attachLists method, add a breakpoint at if (hasArray()) {, and continue. Since you already have a list, you will go to a 1-to-many process

  • At the end of the execution, the current array is outputp array()

thislist_array_tt<method_t, method_list_t>That means you’re going to put a lot of method_list_t in your array, and you’re going to put a lot of method_t in your method_list_t

Conclusion: If there is only one category for this class, we will go to case 3, that is, the 1 to many case

Rwe — LGB classification data loading

If you add another category, LGB, you go to the third case, which is many-to-many

  • Again go toattachCategories -- if (mcount > 0) {And into theattachListsTo the many-to-many situation

  • The current array format isp array()

– p
25 [ 0 ] p 25[0] – p
25[1] – p
25 [ 2 ] p 25[2] – p
Lists [0] -p *$29 29. Lists [0] -p *$29LGBMethod list

The order of output is

conclusion

In summary, the attachLists method mainly loads data for classes and classifications into RWE

  • First, the data of this class is loaded. At this time, no data in RWE is empty, and the 0-to-1 process is followed

  • When a category is added, the RWE has only one list, that is, the list of this class, and goes through the one-to-many process

  • When another category is added, there are two lists in rWE, that is, the list of this category + category, and the many-to-many process is carried out

See the figure below

Lazy-loaded classes and non-lazy-loaded classes

  • On the basis of validation method sorting, continue inrweAdd a breakpoint, which is zeroNULL

  • So let’s go step by step,rweIs still theNULLAnd will not goifThe process inside

In this case, even though the method is processed, it’s not stored from RW to RWE, so here’s the problem. So far, Data -> ro -> rw -> rWE -> realizeClassWithoutSwift (ro, rw) -> methodizeClass

The root cause is that step 9 in the _read_images method implements a non-lazily loaded class, so how do we make a lazily loaded class into a non-lazily loaded class?

The main reason is that we implemented a +load method in LGPerson before we ran objc source code. Otherwise, if we removed the +load method, the class would be lazily loaded and would not end up in the for loop in step 9

So, in summary, the difference between a lazy-loaded class and a non-lazy-loaded class is whether or not the +load method is implemented

  • Implement + load, it isNon-lazily loaded classes.
  • On the other hand, isLazy loading class

Why does implementing the load method make it a non-lazily loaded class?

  • Mainly becauseloadwillLoading in advance(loadMethods inload_imagesThe call,The premiseisClass there)

When is the lazy-loaded class loaded?

  • inA method is calledWhen loading

Debug verifies the timing of lazy class loading

Let’s verify this with code debugging

  • Comment out theLGPersonIn the+loadMethod, and inmainInstantiated inpersonWe add a break point at

  • Add a breakpoint at readClass — main in step 9 of the _read_images for loop

  • Go to realizeClassWithoutSwift — methodizeClass — prepareMethodLists — [person kc_instanceMethod1];

Stack information verification

You can also go throughbtStack information view, method why can come? itsnatureIs because I walked torealizeClassWithoutSwift, which is essentially a callalloc, i.e.,Sending a message

soLazy loading classandNon-lazily loaded classestheData loading timeSee the figure below

conclusion

  • readClassThe main thing is to read the class, that is, the class at this point only address + name, but no data data
  • realizeClassWithoutSwiftThe main one is the implementation class, which reads the data data of the class into memory
    • MethodizeClass implements the serialization of methods (protocols, etc.) in a class

    • The attachCategories method implements data loading for classes and classifications

To sum up,Classes are loaded into memory from Mach-oThe flowchart is shown below