In the previous article, we understood how DYLD and OBJC are related. The main purpose of this article is to understand how the relevant information of the class is loaded into memory, focusing on map_images and load_images.

  • map_images: Mainly managementFile and dynamic libraryAll of thesymbol, i.e.,Class, Protocol, Selector, categoryEtc.
  • load_images: Load executionThe load method

Among themcodethroughcompile, readMach-O executable file, and then read from mach-omemory, as shown in the figure below

map_images: Loads the image file to the memory

_dyLD_OBJC_notify_register (&map_images, load_images, unmap_image) is called from _objc_init in the previous article; ,map_images sends the address directly, so it changes with the outside world.

map_imagesThe source code to explore

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[])
    {
        / /... omit
    
        // 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. Count the number of classes
        int totalClasses = 0;
        int unoptimizedTotalClasses = 0;
        // Code block: scope, local processing, that is, local processing of some events
        {
            / /... omit
        }
    
        / /... 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

The key code is _read_images, which I’ll take a look at separately

_read_imagesThe source code to explore

The source code is a bit long, you can also see the following source code disassembly. Read_images source code:

/*********************************************************************** * _read_images * Perform initial processing of the headers in the linked * list beginning with headerList. * * Called by: map_images_nolock * * Locking: runtimeLock acquired 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, conditional control for a load
    if(! doneOnce) { doneOnce = YES; launchTime = YES; #if SUPPORT_NONPOINTER_ISA
        // Disable non-pointer isa under some conditions.

# if SUPPORT_INDEXED_ISA
        // Disable nonpointer isa if any image contains old Swift code
        for (EACH_HEADER) {
            if (hi->info()->containsSwift()  &&
                hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
            {
                DisableNonpointerIsa = true;
                if (PrintRawIsa) {
                    _objc_inform("RAW ISA: disabling non-pointer isa because "
                                 "the app or a framework contains Swift code "
                                 "older than Swift 3.0");
                }
                break;
            }
        }
# endif

# if TARGET_OS_OSX
        // Disable non-pointer isa if the app is too old
        // (linked before OS X 10.11)
        if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) {
            DisableNonpointerIsa = true;
            if (PrintRawIsa) {
                _objc_inform("RAW ISA: disabling non-pointer isa because "
                             "the app is too old (SDK version " SDK_FORMAT ")", FORMAT_SDK(dyld_get_program_sdk_version())); }}// Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
        // New apps that load old extensions may need this.
        for (EACH_HEADER) {
            if(hi->mhdr()->filetype ! = MH_EXECUTE)continue;
            unsigned long size;
            if (getsectiondata(hi->mhdr(), "__DATA"."__objc_rawisa", &size)) {
                DisableNonpointerIsa = true;
                if (PrintRawIsa) {
                    _objc_inform("RAW ISA: disabling non-pointer isa because "
                                 "the app has a __DATA,__objc_rawisa section"); }}break;  // assume only one MH_EXECUTE image
        }
# endif

#endif

        if (DisableTaggedPointers) {
            disableTaggedPointers();
        }
        
        initializeTaggedPointerObfuscator();

        if (PrintConnecting) {
            _objc_inform("CLASS: found %d classes during launch", totalClasses);
        }

        // namedClasses
        // Preoptimized classes don't go in this table.
        // 4/3 is NXMapTable's load factor
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        // Create table key-value (hash table key-value) for quick lookup
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

        ts.log("IMAGE TIMES: first time tasks");
    }

    //2. Fix @selector confusion during precompilation
    // Fix up@selector References Fixes @selector references
    //sel is not a simple string, but a string with an address
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) continue;

            bool isBundle = hi->isBundle();
            _getObjc2SelectorRefs = _getObjc2SelectorRefs = _getObjc2SelectorRefs
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {// List traversal
                const char *name = sel_cname(sels[i]);
                // Register sel operations to add SEL to the namedSelectors hash table
                SEL sel = sel_registerNameNoLock(name, isBundle);
                if(sels[i] ! = sel) {// When sel and sels[I] address are inconsistent, adjust them to be consistent
                    sels[i] = sel;
                }
            }
        }
    }

    ts.log("IMAGE TIMES: fix up selector references");
    //3, error messy class handling
    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
    // readClass: readClass
    for (EACH_HEADER) {
        if (! mustReadClasses(hi, hasDyldRoots)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }
        // Fetch all classes from the compiled classlist, i.e., get the static segment __objc_classlist from Mach-o, which is a pointer to classref_t type
        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];// The CLS is just an address
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);// Read the class. After this step, the value CLS gets is a name
            // After debugging, the process in if is not executed
            // Initialize the memory required by all lazily loaded classes, but the data of lazily loaded classes is not loaded, even the class is not initialized
            if(newCls ! = cls && newCls) {// Class was moved but not deleted. Currently this occurs 
                // only when the new class resolved a future class.
                // Non-lazily realize the class below.
                // Add lazy-loaded classes to the array
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }

    ts.log("IMAGE TIMES: discover classes");
    
    // fix remapping some classes that were not loaded by the image file
    // Fix up remapped classes
    // Class list and nonlazy class list remain unremapped. The class list and non-lazy class list remain unmapped
    // Class refs and super refs are remapped for message dispatching. Class references and super references are remapped for message distribution
    // After debugging, the process in if is not executed
    // Remap unmapped classes and Super classes. The remapped classes are lazy-loaded classes
    if(! noClassesRemapped()) {for (EACH_HEADER) {
            Class *classrefs = _getObjc2ClassRefs(hi, &count);// Mach-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);__objc_superrefs in Mach_O
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
        }
    }

    ts.log("IMAGE TIMES: remap classes");

#if SUPPORT_FIXUP
    //5, fix some messages
    // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) {
        // _getObjc2MessageRefs Gets the static segment __objc_msgrefs of Mach-O
        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());
        }
        // After debugging, the process in for is not executed
        // The traversal registers the function pointer and fixes the new function pointer
        for (i = 0; i < count; i++) {
            fixupMessageRef(refs+i);
        }
    }

    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif

    bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();
    //6, when there is a protocol in the class: readProtocol readProtocol
    // Discover protocols. Fix up protocol refs.
    // Iterate over all Protocol lists and load the Protocol list into the Protocol hash table
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        // CLS = Protocol. All protocols and objects have similar structures. Isa corresponds to Protocol
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        ASSERT(cls);
        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();
        _getObjc2ProtocolList = _getObjc2ProtocolList;
        // The protocol is read from the compiler and initialized
        protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            // Add protocol to the PROTOCOL_map hash table
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

    ts.log("IMAGE TIMES: discover protocols");

    // Fix up @protocol references
    // 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;
        //_getObjc2ProtocolRefs Gets the static section __objc_protorefs of Mach-o
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            // Compare the current protocol with the same memory address in the protocol list. If not, replace it
            remapProtocolRef(&protolist[i]);// Not executed after code debugging
        }
    }

    ts.log("IMAGE TIMES: fix up @protocol references");
    //8
    // Discover categories. Only do this after the initial category
    // attachment has been done. For 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");

    // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code before
    // this thread finishes its fixups.

    // +load handled by prepare_load_methods()

    // Realize non-lazy classes (for +load methods and static Instances) initializes non-lazy classes and performs rW, RO and other operations: realizeClassWithoutSwift
    // class load processing
    // Lazy loading class - others do not move me, I do not move
    // Implement non-lazy-loaded classes, for load methods and static instance variables
    for (EACH_HEADER) {
        _getObjc2NonlazyClassList GetMach-o static segment __objc_nlclslist non-lazy loaded class list
        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 the table, but it has already been inserted before, so it will not be re-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-lazy-loaded classes (instantiate some information about the class object, such as RW)
            realizeClassWithoutSwift(cls, nil);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");

    // Realize newly-resolved future classes, in case CF manipulates them
    //10. Optimize the classes that are violated for classes that are not processed
    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");
            }
            realizeClassWithoutSwift(cls, nil);
            cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }

    ts.log("IMAGE TIMES: realize future classes");

    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }


    // Print preoptimization statistics
    if (PrintPreopt) {
        static unsigned int PreoptTotalMethodLists;
        static unsigned int PreoptOptimizedMethodLists;
        static unsigned int PreoptTotalClasses;
        static unsigned int PreoptOptimizedClasses;

        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) {
                _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
                             "in %s", hi->fname());
            }
            else if (hi->info()->optimizedByDyld()) {
                _objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
                             "in %s", hi->fname());
            }

            classref_t const *classlist = _getObjc2ClassList(hi, &count);
            for (i = 0; i < count; i++) {
                Class cls = remapClass(classlist[i]);
                if(! cls)continue;

                PreoptTotalClasses++;
                if (hi->hasPreoptimizedClasses()) {
                    PreoptOptimizedClasses++;
                }
                
                const method_list_t *mlist;
                if ((mlist = ((class_ro_t *)cls->data())->baseMethods())) {
                    PreoptTotalMethodLists++;
                    if(mlist->isFixedUp()) { PreoptOptimizedMethodLists++; }}if ((mlist=((class_ro_t *)cls->ISA()->data())->baseMethods())) {
                    PreoptTotalMethodLists++;
                    if (mlist->isFixedUp()) {
                        PreoptOptimizedMethodLists++;
                    }
                }
            }
        }

        _objc_inform("PREOPTIMIZATION: %zu selector references not "
                     "pre-optimized", UnfixedSelectors);
        _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
                     PreoptOptimizedMethodLists, PreoptTotalMethodLists, 
                     PreoptTotalMethodLists
                     ? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists 
                     : 0.0);
        _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
                     PreoptOptimizedClasses, PreoptTotalClasses, 
                     PreoptTotalClasses 
                     ? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
                     : 0.0);
        _objc_inform("PREOPTIMIZATION: %zu protocol references not "
                     "pre-optimized", UnfixedProtocolReferences);
    }

#undef EACH_HEADER
}
Copy the code

_read_images is mainly used to load class information, that is, class, classification, protocol, etc., into the source code implementation of _read_images, which is mainly divided into the following parts:

  • 1, condition control for a load
  • 2. Fix @selector confusion during precompilation
  • 3. Bad, messy class handling
  • 4, fix remapping some classes that were not loaded by the image file
  • 5. Fix some messages
  • 6, When there is a protocol in the class: readProtocol Reads the protocol
  • 7, fix the protocol that was not loaded
  • 8. Classification
  • 9. Class loading processing
  • 10. Optimize the classes that are violated without being processed

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

1, condition control for 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 of which is to find the class easily and quickly.

if(! doneOnce) {/ /... omit
    
    // namedClasses
    // Preoptimized classes don't go in this table.
    // 4/3 is NXMapTable's load factor
    int namedClassesSize = 
        (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
// Create table key-value (hash table key-value) for quick lookup
    gdb_objc_realized_classes =
        NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

    ts.log("IMAGE TIMES: first time tasks");
}
Copy the code

See the comment for gdb_objC_realized_classes. This hash table is used to store named classes that are not in the shared cache, whether implemented or not, and has a capacity of 3/4 of the number of classes.

// This is a misnomer: gdb_objc_realized_classes is actually a list of 
// named classes not in the dyld shared cache, whether realized or not.
// gdb_objC_realized_classes is actually a list of named classes not in the dyLD shared cache, whether implemented or not
NXMapTable *gdb_objc_realized_classes;  // exported for debuggers in objc-gdb.h
Copy the code

2. Fix @selector confusion during precompilation

Sel_registerNameNoLock adds SEL to the namedSelectors hash table by iterating the list to __objc_selrefs from Mach_O via _getObjc2SelectorRefs.

// Fix up@selector References Fixes @selector references
//sel is not a simple string, but a string with an address
static size_t UnfixedSelectors;
{
    mutex_locker_t lock(selLock);
    for (EACH_HEADER) {
        if (hi->hasPreoptimizedSelectors()) continue;

        bool isBundle = hi->isBundle();
        _getObjc2SelectorRefs = _getObjc2SelectorRefs = _getObjc2SelectorRefs
        SEL *sels = _getObjc2SelectorRefs(hi, &count);
        UnfixedSelectors += count;
        for (i = 0; i < count; i++) { // List traversal
            const char *name = sel_cname(sels[i]);
            // Register sel operation to add SEL to
            SEL sel = sel_registerNameNoLock(name, isBundle);
            if(sels[i] ! = sel) {// When sel and sels[I] address are inconsistent, adjust them to be consistentsels[i] = sel; }}}}Copy the code
  • Among them_getObjc2SelectorRefsThe following is the source code ofMach-OStatic segment in__objc_selrefs, subsequent pass_getObjc2At the beginning ofMach-OStatic segment fetch, all corresponding to differentsection 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_registerName, as shown below, its key code isauto it = namedSelectors.get().insert(name);, insert selnamedSelectorsHash table.
SEL sel_registerNameNoLock(const char *name, bool copy) {
    return __sel_registerName(name, 0, copy);  // NO lock, maybe copy
}

👇
static SEL __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);/ / insert table sel
    if (it.second) {
        // No match. Insert.
        *it.first = (const char *)sel_alloc(name, copy);
    }
    return (SEL)*it.first;
}
Copy the code
  • Among themselector --> selIt’s not just a string, it’sA string with an addressAs shown below,sels[i]withselThe character strings are consistent, but the addresses are inconsistent. That is, fix up, which can be printed and debugged as follows:

3. Bad, messy class handling

The main thing is to take all the classes out of Mach-O and process them in traversal

//3, error messy class handling
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
// readClass: readClass
for (EACH_HEADER) {
    if (! mustReadClasses(hi, hasDyldRoots)) {
        // Image is sufficiently optimized that we need not call readClass()
        continue;
    }
    // Fetch all classes from the compiled classlist, i.e., get the static segment __objc_classlist from Mach-o, which is a pointer to classref_t type
    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];// The CLS is just an address
        Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized); // Read the class. After this step, the value CLS gets is a name
        // After debugging, the process in if is not executed
        // Initialize the memory required by all lazily loaded classes, but the data of lazily loaded classes is not loaded, even the class is not initialized
        if(newCls ! = cls && newCls) {// Class was moved but not deleted. Currently this occurs 
            // only when the new class resolved a future class.
            // Non-lazily realize the class below.
            // Add lazy-loaded classes to the array
            resolvedFutureClasses = (Class *)
                realloc(resolvedFutureClasses, 
                        (resolvedFutureClassCount+1) * sizeof(Class));
            resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
        }
    }
}
ts.log("IMAGE TIMES: discover classes");
Copy the code
  • Through code debugging, it is known that the execution is notreadClassMethods before,CLS is just an address:
  • After the execution,clsIs aThe name of the class:

So at this point, the class’s information is currently only stored in the address + name.

4, fix remapping some classes that were not loaded by the image file

The unmapped Class and Super Class are remapped, where

  • _getObjc2ClassRefsIs to obtainMach-OStatic segment in__objc_classrefsnamelyThe references to classes
  • _getObjc2SuperRefsIs to obtainMach-OStatic segment in__objc_superrefsnamelyA reference to a parent class
  • It can be learned from the comments that byremapClassRefThe classes areLazy-loaded classes, so this part of the code was not executed when it was initially debugged.
// fix remapping some classes that were not loaded by the image file
// Fix up remapped classes
// Class list and nonlazy class list remain unremapped. The class list and non-lazy class list remain unmapped
// Class refs and super refs are remapped for message dispatching. Class references and super references are remapped for message distribution
// After debugging, the process in if is not executed
// Remap unmapped classes and Super classes. The remapped classes are lazy-loaded classes
if(! noClassesRemapped()) {for (EACH_HEADER) {
        Class *classrefs = _getObjc2ClassRefs(hi, &count);// Mach-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);__objc_superrefs in Mach_O
        for (i = 0; i < count; i++) {
            remapClassRef(&classrefs[i]);
        }
    }
}

ts.log("IMAGE TIMES: remap classes");
Copy the code

5. Fix some messages

The static segment __objc_MSgrefs of Mach-O is obtained by _getObjc2MessageRefs, and the function pointer is registered through fixupMessageRef and fixed as the new function pointer.

#if SUPPORT_FIXUP
//5, fix some messages
    // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) {
        // _getObjc2MessageRefs Gets the static segment __objc_msgrefs of Mach-O
        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());
        }
        // After debugging, the process in for is not executed
        // The traversal registers the function pointer and fixes the new function pointer
        for (i = 0; i < count; i++) {
            fixupMessageRef(refs+i);
        }
    }

    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
Copy the code

6, When there is a protocol in the class: readProtocol Reads the protocol

//6, when there is a protocol in the class: readProtocol readProtocol
// Discover protocols. Fix up protocol refs. Discover the protocol. Amendment agreement Reference
// Iterate over all Protocol lists and load the Protocol list into the Protocol hash table
for (EACH_HEADER) {
    extern objc_class OBJC_CLASS_$_Protocol;
    // CLS = Protocol. All protocols and objects have similar structures. Isa corresponds to Protocol
    Class cls = (Class)&OBJC_CLASS_$_Protocol;
    ASSERT(cls);
    // Get 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();
    _getObjc2ProtocolList = _getObjc2ProtocolList;
    // The protocol is read from the compiler and initialized
    protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
    for (i = 0; i < count; i++) {
        // Add protocol to the PROTOCOL_map hash table
        readProtocol(protolist[i], cls, protocol_map, 
                     isPreoptimized, isBundle);
    }
}

ts.log("IMAGE TIMES: discover protocols");
Copy the code
  • NXMapTable *protocol_map = protocols(); Create a protocol hash table with the name protocol_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
  • Get the static segment __objc_protolist protocol list in Mach-O with _getObjc2ProtocolList, that is, read from the compiler and initialize protocol

    protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
    Copy the code
  • Loops through the list of protocols, adding protocols to the PROTOCOL_MAP hash table using the readProtocol method

    readProtocol(protolist[i], cls, protocol_map, 
                             isPreoptimized, isBundle);
    Copy the code

7, fix the protocol that was not loaded

Get the static section __objc_protorefs (not the same thing as __objc_protolist in 6) from Mach-O and loop over the protocol that needs to be fixed. The remapProtocolRef command is used to compare the current protocol with the same memory address in the protocol list. If different protocols are used, the protocol is replaced.

// Fix the protocol that was not loaded
// Fix up @protocol references
// 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;
    //_getObjc2ProtocolRefs Gets the static section __objc_protorefs of Mach-o
    protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
    for (i = 0; i < count; i++) {/ / traverse
        // Compare the current protocol with the same memory address in the protocol list. If not, replace it
        remapProtocolRef(&protolist[i]);// Not executed after code debugging
    }
}

ts.log("IMAGE TIMES: fix up @protocol references");
Copy the code

Where remapProtocolRef source code 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();
    // Get the protocol for the uniform memory address in the protocol list
    protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref);
    if(*protoref ! = newproto) {// If the current protocol is different from the same memory address protocol, replace it*protoref = newproto; UnfixedProtocolReferences++; }}Copy the code

8. Classification

It deals primarily with classifications that need to be initialized and loaded into the class, and for classifications that occur at runtime, the discovery of classifications is deferred until the first load_images call after the call to _dyLD_OBJC_Notify_register is complete.

//8
// Discover categories. Only do this after the initial category
// attachment has been done. For 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

// Realize non-lazy classes (for +load methods and static Instances) initializes non-lazy classes and performs rW, RO and other operations: realizeClassWithoutSwift
    // Lazy loading class - others do not move me, I do not move
    // Implement non-lazy-loaded classes, for load methods and static instance variables
    for (EACH_HEADER) {
        _getObjc2NonlazyClassList GetMach-o static segment __objc_nlclslist non-lazy loaded class list
        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 the table, but it has already been inserted before, so it will not be re-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-lazy-loaded classes (instantiate some information about the class object, such as RW)
            realizeClassWithoutSwift(cls, nil);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");
Copy the code

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

  • through_getObjc2NonlazyClassListGets the static segment of Mach-O__objc_nlclslistNon-lazily loaded class table
  • throughaddClassTableEntryInsert non-lazily loaded classes into the class table and store them in memory. If they are already added, they will not be added. Ensure that the entire structure is added
  • throughrealizeClassWithoutSwiftImplement the current class because the previous3In thereadClassThe only memory read isAddress + Name, the class ofdataThe data is not loaded.

10. Optimize the classes that are violated without being processed

The main idea is to implement classes that are not handled and optimize classes that are 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");
            }
            / / implementation class
            realizeClassWithoutSwift(cls, nil);
            cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }

    ts.log("IMAGE TIMES: realize future classes");

    if (DebugNonFragileIvars) {
        // Implement all classes
        realizeAllClasses();
    }
Copy the code

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

Below we take out separately to explain.

ReadClass: Reads the class

CLS is the name of the class. The key code is addNamedClass and addClassTableEntry: addNamedClass addClassTableEntry

/*********************************************************************** * readClass * Read a class and metaclass as written by a compiler. Returns the new class pointer. This could be: Returns a new class pointer, possibly:  * - 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();/ / name
    
    ** ---- If you want to enter a custom, add a judgment yourself
    const char *LGPersonName = "LGPerson";
    if (strcmp(mangledName, LGPersonName) == 0) {
        auto kc_ro = (const class_ro_t *)cls->data();
        printf("%s -- Research focus --%s\n", __func__,mangledName);
    }
    // Return nil if there are missing weak-Linked classes in the parent of the current class
    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();
// Determine if it is a class to be processed later
    // Normally, you do not go to popFutureNamedClass because this is an operation that is specific to future classes
    // Through the breakpoint debugging, does not go into the if process, therefore does not operate on ro, RW
    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());
        }
        // Read class data, set ro, rw
        // After debugging, it will not go here
        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 has been 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 classes in the shared cache
        addClassTableEntry(cls);// Insert the table, i.e. read from the Mach-o file into memory
    }

    // for future reference: 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

Source code implementation, mainly divided into the following steps:

  • throughmangledNameGets the name of the class wheremangledNameMethod source code implementation is as follows:
const char *mangledName() { 
        // fixme can't assert locks here
        ASSERT(this);

        if (isRealized()  ||  isFuture()) { // This initialization judgment is similar in lookupImp
            return data()->ro()->name;// If already instantiated, get name from ro
        } else {
            return ((const class_ro_t *)data())->name;// Instead, get name from mach-o's data}}Copy the code
  • Returns nil if there are missing weak-Linked classes in the parent of the current class

  • In normal circumstances, popFutureNamedClass will not go to, because this is specifically for the future class to be processed operations, you can also use breakpoint debugging, know that it will not go to the if process, so ro, RW will not operate

    • dataismach-OData, andMemory not in classIn the
    • rotheThe assignmentfromData in Mach -O is strongly assignedthe
    • Ro rwfromRo copy pastthe
  • AddNamedClass adds the current class to the created gDB_objC_realized_classes hash table, which holds all classes.

    / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * addNamedClass load sharing classes in the cache insert table * Adds name = > cls to the named non-meta class map. Adds name=> CLS to named non-metaclass 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 tableNXMapInsert(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
  • AddClassTableEntry adds the initialized class to the allocatedClasses table in iOS- Underlying Principles 16: The allocatedClasses table is created by runtime_init in _objc_init. AddClassTableEntry:

    / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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();Runtime_init in objc_init creates the table
    
        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

Debugging tip: If we want to break points in the source codeLocate the custom class, you canCustom add if judgmentBreak only when needed

In summary, readClass is used to read classes from Mach-O into memory, i.e. insert them into a table. However, the current class has only two pieces of information: address and name, and the mach-O data has not been read.

Let’s look at the realizeClassWithoutSwift method in 9.

RealizeClassWithoutSwift: Implementation class

9 in realizeClassWithoutSwift method. RealizeClassWithoutSwift complete source code as follows:

/*********************************************************************** * realizeClassWithoutSwift * Performs first-time initialization on class cls, * including allocating its read-write data. * Does not perform any Swift-side initialization. * Returns the real class structure for the class. * Locking: runtimeLock must be write-locked by the caller **********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;
    
    const char *mangledName  = cls->mangledName();
    const char *LGPersonName = "LGPerson";

    if (strcmp(mangledName, LGPersonName) == 0) {
        auto kc_ro = (const class_ro_t *)cls->data();
        auto kc_isMeta = kc_ro->flags & RO_META;
        if(! kc_isMeta) { printf("%s: This is my research %s \n",__func__,LGPersonName); }}if(! cls)return nil;
    if (cls->isRealized()) return cls;
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?
// [first step] read data and set ro and Rw
    // Read the class's data() and create the ro/ RW
    auto ro = (const class_ro_t *)cls->data();// read the bits attribute of the class structure. //ro -- clean memory is determined at compile time
    auto isMeta = ro->flags & RO_META;// Determine the metaclass
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();//dirty memory assignmentro = cls->data()->ro(); ASSERT(! isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); }else {// Now the data is read in and the assignment is complete
        // Normal class. Allocate writeable class data. Open and assign data
        rw = objc::zalloc<class_rw_t>();// apply zalloc -- rW
        rw->set_ro(ro);// Set ro in rW to temporary variable ro
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);// Assign the CLS data to the RW form
    }

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    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)" : "");
    }
Call realizeClassWithoutSwift recursively to complete the inheritance chain
    // 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.
    // The recursive implementation sets the rW of the current class, parent class, and metaclass. The main purpose is to determine the inheritance chain (class inheritance chain, metaclass inheritance chain)
    // Implement metaclass, superclass
    // When isa finds the root metaclass, the isa of the root metaclass refers to itself and does not return nil, resulting in an infinite loop. If not, the current class is returned, ensuring that the class is loaded only once and ending the recursion
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if(! hackedDispatch &&0 == strcmp(ro->name, "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->superclass  &&
                 supercls->instancesRequireRawIsa())
        {
            // This is also propagated by addSubclass()
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate
            // from root class to root metaclass
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if(instancesRequireRawIsa) { cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited); }}// SUPPORT_NONPOINTER_ISA
#endif
    The classes that give us the parent and metaclass are the corresponding values of isa and the parent, respectively
    // Update superclass and metaclass in case of remapping class
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if(supercls && ! isMeta) reconcileInstanceVariables(cls, supercls, ro);// Set fastInstanceSize if it wasn't set already.
    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(); }}// Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass's subclass lists
    // The bidirectional list points to the parent class of the relation and the parent class can be found in the child class
    // Add the current class to the subclass list of the parent class
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }
MethodizeClass = methodizeClass = methodizeClass
    // Attach categories to categories -- Add categories to ro and add categories to RW
    // Write ro data to rW
    methodizeClass(cls, previously);

    returncls; }}Copy the code

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

The main function of realizeClassWithoutSwift method is to realize the class and load the data data of the class into memory. The main steps are as follows:

  • [Step 1] ReaddataData and setRo, rw
  • [Step 2] Recursive callrealizeClassWithoutSwiftperfectInheritance chain
  • [Step 3] PassmethodizeClassMethods the class

[First step] Read data

// fixme verify class is not in an un-dlopened part of the shared cache?
// Read the class's data() and create the ro/ RW
auto ro = (const class_ro_t *)cls->data(); // read the bits attribute of the class structure. //ro -- clean memory is determined at compile time
auto isMeta = ro->flags & RO_META; // Determine the metaclass
if (ro->flags & RO_FUTURE) {
    // This was a future class. rw data is already allocated.
    rw = cls->data(); //dirty memory assignmentro = cls->data()->ro(); ASSERT(! isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); }else { // Now the data is read in and the assignment is complete
    // Normal class. Allocate writeable class data.
    rw = objc::zalloc<class_rw_t>(); // apply zalloc -- rW
    rw->set_ro(ro);// Set ro in rW to temporary variable ro
    rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
    cls->setData(rw);// Assign the CLS data to the RW form
}
Copy the code

Read class data and convert it to RO, rW initialization and ro copy to RO in RW.

  • rosaidreadOnly, i.e.,read-only, which contains information about class names, methods, protocols, and instance variables. Since it is read-only, it belongs toClean MemoryAnd theClean MemoryRefers to theMemory that does not change after loading
  • rwsaidreadWrite, i.e.,Can read but writeDue to its dynamic nature, it is possible to add attributes, methods, and protocols to classes in the latest 2020WWDCtheMemory optimizationinstructionsAdvancements in the Objective-C runtime – WWDC 2020 – Videos – Apple DeveloperMentioned,rw, in fact, inrwOnly 10% of the classes actually change their methods, so there arerwe, i.e.,Additional information about the class. For those classes that do require additional information, it can be assignedrweExpand one of the records and delimit itclassFor its use. Among themrwBelong todirty memoryAnd thedirty memoryRefers to theMemory that changes while the process is running.Class structureAs soon asuseIt will become aditry memoryBecause theThe runtimeNew data is written to it, such as creating a new method cache and pointing to it from the class.

[Step 2] Recursively call realizeClassWithoutSwift to complete the inheritance chain

 // 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. --
    // Call realizeClassWithoutSwift recursively to complete the inheritance chain and handle the parent, metaclass of the current class
    // The recursive implementation sets the rW of the current class, parent class, and metaclass. The main purpose is to determine the inheritance chain (class inheritance chain, metaclass inheritance chain)
    // Implement metaclass, superclass
    // When isa finds the root metaclass, the isa of the root metaclass refers to itself and does not return nil, resulting in an infinite loop. If not, the current class is returned, ensuring that the class is loaded only once and ending the recursionsupercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); .// Update superclass and metaclass in case of remapping
The classes that give us the parent and metaclass are the corresponding values of isa and the parent, respectivelycls->superclass = supercls; cls->initClassIsa(metacls); .// Connect this class to its superclass's subclass lists
// The bidirectional list points to the parent class of the relation and the parent class can be found in the child class
// Add the current class to the subclass list of the parent class
if (supercls) {
    addSubclass(supercls, cls);
} else {
    addRootClass(cls);
}
Copy the code

Recursive calls to realizeClassWithoutSwift complete the inheritance chain and set the RW of the current class, parent class, and metaclass

  • Recursive callsrealizeClassWithoutSwiftSet up theSuperclass, metaclass
  • Set up theIsa pointing to parent and metaclass
  • throughAddSubclass and addRootClassSets the parent’s bidirectional linked list pointing relationship, i.eSubclasses can be found in the parent class, and subclasses can be found in the parent class

There’s a problem with realizeClassWithoutSwift recursion call, when isa finds the root metaclass, the isa of the root metaclass is pointing to itself, it doesn’t return nil, so there’s the following recursive termination condition, the purpose of which is to ensure that the class is only loaded once

  • inrealizeClassWithoutSwiftIn the
    • If a classThere is no, the returnnil
    • If a classHave been implemented, directly returnscls
    static Class realizeClassWithoutSwift(Class cls, Class previously)
    {
        runtimeLock.assertLocked();
    
        // If the class does not exist, return nil
        if(! cls)returnnil; If the class is already implemented, CLS is returned directlyif (cls->isRealized()) return cls;
        ASSERT(cls == remapClass(cls));//remapClass: return nil if 'CLS' does not exist. }Copy the code

MethodizeClass class

MethodizeClass reads the list of methods (including methods in the classification), attribute list, protocol list from RO, assigns values to RW, and returns CLS.

// Attach categories to categories -- Add categories to ro and add categories to RW
// Write ro data to rW
methodizeClass(cls, previously);

return cls;
Copy the code

The details will be discussed separately below.

Breakpoint debug realizeClassWithoutSwift

If we need to track custom classes, we also need to_read_imagesStep 9 of the methodrealizeClassWithoutSwiftBefore calling, as wellrealizeClassWithoutSwiftCustom logic was added to the method primarily for convenienceDebug custom classes._read_imagesStep 9 of the methodrealizeClassWithoutSwiftAdd custom logic before call realizeClassWithoutSwiftMethod to add custom logicNow, let’s start our breakpoint debugging

inLGPersonThe rewrite+loadfunction

  • Rerun the program, and there we are_read_imagestheStep 9Custom logic section in

  • inrealizeClassWithoutSwiftCall part with breakpoint, run and break

  • Continue running the program and the breakpoint arrivesrealizeClassWithoutSwiftMethod to customize the judgment in the code

  • Continue toauto ro =Add breakpoint, continue to run, break – this part is mainlyRead the data Check the ro

  • Elserw->set_ro(ro);Add a break point, stop and lookrwFor the time of,rwis0x0, view the RW, which includesro 和 rwe

x/4gx clsThe red box is 0x0

  • Go ahead and lookx/4gx cls, is still0x0 This is where we need to lookset_roSource code implementation, the 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

    According to the source coderotheTo obtainThere are two main cases:Is there a runtime
    • ifWith the runtime, fromrwReads the
    • Conversely, if there is no runtime fromroReads the

MethodizeClass method class

RealizeClassWithoutSwift in methodizeClass source code implementation is as follows:

static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data(); // Initialize an RWauto ro = rw->ro(); auto rwe = rw->ext(); .// Install methods and properties that the class implements itself.
    // Paste property list, method list, protocol list, etc to rW
    // Add the list of methods from ro to rW
    method_list_t *list = ro->baseMethods();// Get the baseMethods of ro
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));//methods for sorting
        if (rwe) rwe->methods.attachLists(&list, 1);// Process the RWE
    }
    // Add attributes
    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }
    // Join the 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.
    // Add a method to the classification
    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

It is mainly divided into several parts:

  • willProperty list, method list, protocol listSuch as postrweIn the
  • additionalclassification(explained in the next article)
rweThe logic of the

The logic for adding the list of methods to RWE is as follows:

  • To obtainrothebaseMethods
  • throughprepareMethodListsMethods the sorting
  • rightrweProcess and passattachListsinsert

Let’s look at how the prepareMethodLists method is sorted.

prepareMethodListsMethods the sorting

In the message process of slow search process iOS- underlying principle 13: message process analysis of slow search article, the method of search algorithm is through binary search algorithm, sel-IMP is sorted, so how to sort it? PrepareMethodLists source code is as follows:

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*/);/ / sorting}}... }Copy the code

Internally, sort with fixupMethodList.

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) {
            constchar *name = sel_cname(meth.name); meth.name = sel_registerNameNoLock(name, bundleCopy); }}// Sort by selector address. Sort by SEL 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

It’s sort by selector Address. We can do breakpoint verification.

Validation method sorting
  • inmethodizeClassMethod to add custom logic and breakreadroThe –baseMethodList(byauto kc_ro = kc_rw->ro(); -- ro() -- class_ro_tType view properties)

  • Enter theprepareMethodListsMethod source, add custom breakpoints (mainly for targeted research), execute breakpoints, run to custom logic and break (add herekc_isMeta, mainly used for filtering outMethods in a metaclass of the same name)

  • Came tofixupMethodList, i.e.,Sequence of sel

  • Enter thefixupMethodListSource code implementation, (According to the selAdress selSort), break again to the following part, that is, the method has gone through one level of sorting.Print and have a look:

So sort before and aftermethodlistFor example, the following is the summary:methodizeClassIn thefixupMethodListI’m going to put ro inbaseMethodsTo sort,methodizeClassMethod in the implementation classmethodsAgreement, etcserialization.

Return to methodizeClass source code to continue. The attachToClass method is called in the methodizeClass source code. The main function is to add the classification to the main class.

attachToClassmethods

The source code 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 my research %s \n",__func__,LGPersonName); } } auto &map = get(); auto it = map.find(previously); If (it! Category_list &list = it->second; category_list = it->second; category_list = it->second; If (flags & ATTACH_CLASS_AND_METACLASS) {// determine whether to 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); AttachCategories attachCategories(CLS, list.array(), list.count(), flags); attachCategories(CLS, list.array(), flags); } map.erase(it); }}Copy the code

Because the external loop in attachToClass (_read_images->realizeClassWithoutSwift->methodizeClass->attachToClass, while realizeClassWithoutSwift is _R Loop multiple times in eAD_images) when a category is found, it goes to attachCategories once.

AttachCategories method

Prepare the classified data in the attachCategories method, its source code implementation is as follows:

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 we initialize rWE here? Because now we need to do one thing: add attributes, methods, protocols, etc. */ to this class
    auto rwe = cls->data()->extAllocIfNeeded();
        
    // Mlists are a two-dimensional array
    for (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) {McOunt = 0, ATTACH_BUFSIZ= 64
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle);// Get ready to sort
                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);/ / sorting
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);//mlists + attach_bufsiz-mcount Indicates memory translation
        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
  • In auto rwe = CLS ->data()->extAllocIfNeeded(); Rwe creation, so why rWE initialization here? Because now we have one thing to do: add attributes, methods, protocols, and so on to this class, which is to process the old Clean Memory

    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) { // Check whether rwe exists
            return v.get<class_rw_ext_t *>();// If it exists, get it directly
        } else {
            return extAlloc(v.get<const class_ro_t *>());// If it does not exist, open it}} 👇//extAlloc source code implementation
    class_rw_ext_t *
    class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
    {
        runtimeLock.assertLocked();
        // Add data to rWE (0-1)
        auto rwe = objc::zalloc<class_rw_ext_t>();/ / create
    
        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
    • Enter the source code implementation of the extAllocIfNeeded method to determine whether rWE exists, if it does, directly obtain, if not open

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

  • The key code is RWE ->methods.attachLists(mlists + attach_bufsiz-mcount, McOunt); That is, stored at the end of mlists, the data for mlists comes from the for loop in front

  • At debug run time, it was found that the name in CATEGORY_T was compiled as LGPerson (see clang compile) and at run time as LGA, the name of the classification

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

Conclusion: This class needs to add attributes, methods, etc., so it needs to initialize RWE. The initialization of RWE mainly involves classification, addMethod, addProperty, and AddProtocol, that is, the initialization of RWE will only be carried out 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 code implementation
void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return; The situation1:Many to manyif (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;
        // Based on the new capacity, create an array of type array_t, obtained from array()
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        // Set the array size
        array()->count = newCount;
        // Old data stores old lists from the subscript of the addedCount array. The size of old data * the size of a single old list
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));
        // New data is stored at the top of the array. New lists are stored. The size of the new data is the size of a single list
        memcpy(
               array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0])); } 【 situation2:` 0 for a `else if(! list && addedCount ==1) {
        // 0 lists -> 1 list
        list = addedLists[0];// Add list to the first element of mlists. The list is a one-dimensional array} 【 situation3:One to manyelse {
        // 1 list -> many lists have a list
        // The new list is the classification, from LRU's algorithmic thinking, namely the least recently used
        // Get the old list
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        // Calculate capacity sum = Number of old lists + number of new lists
        uint32_t newCount = oldCount + addedCount;
        // Create a collection of capacities and sizes of type array_t, i.e. create an array, place it in array, and fetch it from array()
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        // Set the array size
        array()->count = newCount;
        // Add the old list to the end of the array
        if (oldList) array()->lists[addedCount] = oldList;
        // memcpy (start position, what, how big) is memory shift, store new list from the start of array
        Array ()->lists indicates the first element position
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0])); }}Copy the code

From the source can be learned, insert table is mainly divided into three cases:

  • [Case 1: many-to-many] If there are multiple one-dimensional arrays in the two-dimensional array of attachLists list_ARRAY_TT currently called

    • Compute arrayThe old listsThe size of the
    • Calculate the new capacity size =Old data size + new data size
    • Based on the new capacity size, open an array of typearray_tThrough thearray()To obtain
    • Set array size
    • The old dataStore old lists starting with the addedCount array subscript, the size ofOld data size * size of a single old list, i.e.,The whole paragraph translationCan be simply understood asThe original data is moved to the back, i.e.,The pointer offset
    • The new dataStore the new lists starting at the top of the array, the size ofNew data size * single list sizeCan be simply understood asThe later you add it, the more in front, the more in front, the call takes precedence
  • If attachLists’ LIST_ARRAY_tt two-dimensional array is empty and the number of new sizes is 1

    • Direct assignmentaddedListtheThe first list
  • If the list_ARRAY_TT two-dimensional array currently called 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 capacities and sizes of type array_t, that is, create an array, place it in array, and fetch it from array()

    • Sets the size of the array

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

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

For case 3, lists here refer to classifications

  • That’s why, in everyday development, a subclass implementing a superclass method overwrites a superclass method

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

  • This operation comes from an algorithmic thinking that LRU is the least recently used, and the purpose of adding this newList is to use the methods in this newList, which is of high value to the user, i.e. call first

  • The main reason for the 1-to-many phenomenon is the addition of categories, that is, the old elements come after and the new elements come before. The fundamental reason is that category is called first, which is also the significance of classification

Difference between memmove and memcpy

  • If you don’t know how much memory you need to shift, you needmemmoveforTranslation memory.Ensure safety
  • memcpyCopy a number of bytes from the starting location of the original memory address to the target memory address.Speed is fast
Rwe data loading

Example project:

main.m
#import "LGPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        [person kc_instanceMethod1];
        NSLog(@"%p",person);
    }
    return 0;
}
-------
LGPerson.h
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *kc_name;
@property (nonatomic, assign) int kc_age;

- (void)kc_instanceMethod1;
- (void)kc_instanceMethod2;
- (void)kc_instanceMethod3;

+ (void)kc_sayClassMethod;
@end
-------
LGPerson.m
@implementation LGPerson

+(void)load{
    NSLog(@"%s",__func__);
}

- (void)kc_instanceMethod1{
    NSLog(@"%s",__func__);
}

- (void)kc_instanceMethod2{
    NSLog(@"%s",__func__);
}

- (void)kc_instanceMethod3{
    NSLog(@"%s",__func__);
}

+ (void)kc_sayClassMethod{
    NSLog(@"%s",__func__);
}
@end
------
LGPerson+LGA.h
@interface LGPerson (LGA)
- (void)cateA_1;
- (void)cateA_2;
- (void)cateA_3;
@end
-----
LGPerson+LGA.m
@implementation LGPerson (LGA)
- (void)kc_instanceMethod1{
    NSLog(@"%s",__func__);
}

- (void)cateA_1{
    NSLog(@"%s",__func__);
}
- (void)cateA_2{
    NSLog(@"%s",__func__);
}
- (void)cateA_3{
    NSLog(@"%s",__func__);
}
@end
-----
LGPerson+LGB.h
@interface LGPerson (LGB)
- (void)cateB_1;
- (void)cateB_2;
- (void)cateB_3;
@end
------
LGPerson+LGB.m
@implementation LGPerson (LGB)

- (void)kc_instanceMethod1{
    NSLog(@"%s",__func__);
}
- (void)cateB_1{
    NSLog(@"%s",__func__);
}
- (void)cateB_2{
    NSLog(@"%s",__func__);
}
- (void)cateB_3{
    NSLog(@"%s",__func__);
}
@end
Copy the code

1 ️ Rwe — data loading of LGPerson on this class

The next step is to verify the rWE data 0-1 process by debugging, adding the class’s method list

  • Add custom logic to attachCategories -> extAllocIfNeeded -> extAlloc, run, and break From the attachCategories method auto rwe = CLS ->data()->extAllocIfNeeded(); It’s here. It’s here to open up rWE,

    • So why do we initialize rWE here? Because now we have one thing to do: add attributes, methods, protocols, and so on to this class, which is to process the old Clean Memory

    • rweIs in theClassification process, that is, rWE initialization, and there are several methods involved in RWE initialization, respectively:Category + addMethod + addPro + addProtocol

  • p rwe

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

Continue to if (list) {break

  • p list
  • p *$2For the time of,listisLGPersonA list of methods for this class
  • inattachListsIn the methodif (hasArray()) {Set a breakpoint, run the break, and continue, which leads to the else-if process, 0 on 1 —LGPerson Add to the list of methods of this classWill go0 to 1 process
  • p addedLists, is the address of a list pointer, givenmlistsIs the first element of typemethod_list_t *const *
  • p addedLists[0]
  • p *$5
  • p addedLists[1]
  • p *$7, will also have values, mainly because memory is contiguous and accessed by someone else conclusion: so,0 to 1It is a kind ofA one-dimensional assignment, the function path is:map_images -> _read_images -> readClass -> realizeClassWithoutSwift -> methodizeClass -> prepareMethodLists -> fixupMethodList -> attachToClass -> load_categories_nolock -> attachCategories -> extAllocIfNeeded -> extAlloc -> attachLists

2 ️ RWE — LGA classification data loading Go ahead and print the list

  • p list, where the list ismethod_list_tstructure

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

    • p mlist
    • P *$10, then the Mlist is classified LGA

  • inif (mcount > 0) {Partially add a breakpoint, continue execution, and break

  • I’m going to do one step downmlists 为Set of sets Among themmlists + ATTACH_BUFSIZ - mcountforTranslation memory

    • p mlists + ATTACH_BUFSIZ - mcount, because McOunt = 1 and ATTACH_BUFSIZ = 64, it moves from the first to the 63rd bit, the last element
    • p *$14
    • p *$15 ,mlistsThe class capacity of the last element isA list of methods for this class
  • Enter theattachListsMethods,if (hasArray()) {Add a breakpoint, continue, since you already have a list, it will go toMore than 1 toThe process of

  • At the end of the command, the current array is outputp array() thislist_array_tt<method_t, method_list_t>So we’re going to put a lot of method_list_t in array, a lot of method_t in method_list_t.

    Summary: If this class has only one classification, it will go to case 3, which is a 1 to many case.


3 ️ RWE — LOADING of LGB classified data If you add another classification LGB, you get to the third case, which is many-to-many.

  • Again go toattachCategories -- if (mcount > 0) {And into theattachListsLet’s go to many-to-many
  • To view the current array format, i.ep array() P star $29, the first one that’s stored in thereLGBList of methods
Rwe data loading summary

To sum up, the attachLists method is mainly to load data of classes and categories into RWE

  • First of all,Load the data of this classFor the time of,Rwe has no data nullGo,0 to 1 process
  • whenAdd a categoryRwe at this timeThere's only one list, the list of this class, go1 For multiple processes
  • When I add another category, this timeThere are two lists in rWE, i.e.,List of classes + categoriesGo,Many-to-many processes

As shown below:

Lazy-loaded and non-lazy-loaded classes

  • On the basis of verification method sequencing, continue torweAdd a breakpoint, which is zeroNULL
  • As I continue to do this,rweIs still theNULLAnd will not goifThe process inside

In this case, although the method is processed, it is not stored from RW to RWE, so the problem is that so far, Data -> ro -> rw -> methodizeClass -> methodizeClass -> methodizeClass

The root cause is that step 9 in the _read_images method implements a non-lazy-loaded class. So how do we turn a lazy-loaded class into a non-lazy-loaded class?

The main thing is that we implemented a +load method in LGPerson before running objC source code, whereas if we removed the +load method, we would be lazy to load the class and not go to step 9 in the for loop.

So, to sum up, the difference between a lazy-loaded class and a non-lazy-loaded class is whether the +load method is implemented.

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

Why does implementing load become a non-lazy-loaded class?

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

When do lazy classes load?

  • inA method is calledWhen loaded.
Debugging verifies when lazy classes load

This is verified by code debugging

  • Comment out theLGPersonIn the+loadMethods (LGA and LGB classificationIt didn’t happen+load), and inmainInstantiated inpersonAdd a breakpoint at
  • in_read_imagesThe first9stepforLoop with a breakpoint —ReadClass -- breakpoint at main
  • I’m going to go down torealizeClassWithoutSwift -- methodizeClass -- prepareMethodLists -- [person kc_instanceMethod1];

This can also be verified by printing stack information.

Print stack information validation

throughbtStack view, method why can come? itsnatureIs because of the wayrealizeClassWithoutSwift, its essence is to callalloc, i.e.,Sending of messages.

soLazy loading classandNon-lazy-loaded classestheData loading timeAs shown below:

conclusion

  • readClassThe main is to read the class, that is, at this time the class only address + name, no data data
  • realizeClassWithoutSwiftThe main implementation class is to read the data of the class into memory
    • methodizeClassMethod in the implementation classmethodsSerialization of (a protocol, etc
    • attachCategoriesMethod implementationClass and classification data loading

To sum up, the flow chart for loading a class from Mach-O into memory looks like this:

reference

This article learns to reference iOS- Basic Principle 17: class loading (1), thanks here

The article lists

List of blog posts