preface

Class loading is divided into lazy loading classes and non-lazy loading classes. They have different loading processes. Now let’s explore the loading of categories and the different loading situations between categories and main classes

The preparatory work

  • Objc – 818.2 –
  • MachOView tools

Classification loading

The underlying structure of classification is categor_t, which is generally loaded into the main class. When and how it is loaded

Class loading introduction

In WWDC, Apple added a special memory rWE for classification and dynamic. Rwe is a dirty memory, so it must be dynamically allocated. Below from class_rw_t to find the related rWE source

struct class_rw_t {.// Omit part of the code
    class_rw_ext_t *ext(a) const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
    }

    class_rw_ext_t *extAllocIfNeeded(a) {
        auto v = get_ro_or_rwe(a);// Determine if rWE exists
        if (fastpath(v.is<class_rw_ext_t* > ())) {Return address pointer if rWE already exists
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
        } else {
            // Open memory for rWE and return address pointer
            return extAlloc(v.get<const class_ro_t*>(&ro_or_rw_ext)); }}class_rw_ext_t *deepCopy(const class_ro_t *ro)
        return extAlloc(ro, true); }...// Omit part of the code
}
Copy the code

The source code shows that extAllocIfNeeded() must be called if you mainly use RWE. Global search extAllocIfNeeded(), There are many calls to attachCategories, demangledName, class_setVersion, addMethods_finish, class_addProtocol, _class_addProperty, and objc_du PlicateClass method. Some of these methods are for dynamic addition, for classification, and some for class names, for setting the version of classes, and for fixing classes. Today we are exploring the loading process of categories, so attachCategories is located

attachCategoriesBackstepping method

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t 
cats_count, int flags)
{...// Omit part of the code
    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;
    / / for rwe
    auto rwe = cls->data() - >extAllocIfNeeded(a); .// Iterate through all the categories, but the methods in the categories should not exceed 64 categories, otherwise Apple will think you have a problem
    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 you have more than 64 categories, load the list of methods for those 64 categories into the main class
        // Put the following data into mlists[]
        if (mcount == ATTACH_BUFSIZ) {
            prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
            rwe->methods.attachLists(mlists, mcount);
            mcount = 0; // Set McOunt to 0
        }
        // If McOunt = 0, mlist is stored in 63 locations, 0 ~ 63 in total
        mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
        fromBundle |= entry.hi->isBundle(a); }/ / properties
      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;
    }
    / / agreement
    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) {
    // Sort the classification methods before adding them to the main class
    Mlists = d n = attach_bufsiz-mcount
    //mlists + ATTACH_BUFSIZ - mcount = d + n
    // At this point mlists + attach_bufsiz-mcount is a two-dimensional pointer that holds the first address of the method list
    prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                       NO, fromBundle, __func__);
    // Add the classification method to the main class method
    rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
    if (flags & ATTACH_EXISTING) {...}
} 
    
    // Add the category attribute to the main class attribute
    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
    // Add the class protocol to the main class protocol
    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount); .// Omit part of the code
} 
Copy the code

AttachCategories prepares the classified data, then calls attachLists to add the data to the main class, so where to call attachCategories method, search globally for attachCategories

The source code shows attachToClass calls the attachCategories method

The source code shows that load_categories_NOLock calls the attachCategories method

attachToClassprocess

Search globally for attachToClass, attachToClass is invoked only by methodizeClass

static void methodizeClass(Class cls, Class previously)
{...// Omit part of the code
    // Attach categories.
    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); .// Omit part of the code
Copy the code
  • methodizeClassThis is a familiar methodrealizeClassWithoutSwiftCall,previouslyA condition determines whether to invoke the one inside the conditionattachToClassMethod, andpreviouslyIt’s passed in as an argument. Finally found isrealizeClassWithoutSwiftIt’s passed in. Global searchrealizeClassWithoutSwift, where the call is passed inpreviously = nil, soattachToClassThe inside of thepreviouslyThe offer won’t go. It has to be finalattachToClassmethods
  • previouslyAs an alternate parameter, this design may be used by Apple for internal debugging
  • attachToClassProcess:_read_images –> realizeClassWithoutSwift –> methodizeClass –> attachToClass –> attachCategories –> attachLists

load_categories_nolockprocess

Search globally for load_categories_NOLock

static void loadAllCategories(a) {
    mutex_locker_t lock(runtimeLock);
    for (auto*hi = FirstHeader; hi ! =NULL; hi = hi->getNext()) {
        load_categories_nolock(hi); }}Copy the code

Call load_categories_NOLock in the loadAllCategories method. Search globally for loadAllCategories

static bool  didInitialAttachCategories = false;

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if(! didInitialAttachCategories && didCallDyldNotifyRegister) { didInitialAttachCategories =true;
        loadAllCategories(a); }// Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;
    recursive_mutex_locker_t lock(loadMethodLock);
    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }
    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods(a); }Copy the code

Load_images calls loadAllCategories, load_images is called in dyld

  • didInitialAttachCategoriesThe default isfalse, when the execution is finishedloadAllCategories()Automatically afterdidInitialAttachCategoriesSet totrueIt’s called only onceloadAllCategories()
  • whenobjctodyldAfter completing the registration callbackdidCallDyldNotifyRegister = true
  • load_categories_nolockProcess:load_images –> loadAllCategories –> load_categories_nolock –> load_categories_nolock –> attachLists

attachLists

The core method is attachLists, which loads the classification data into the main class

 void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;
        //hasArray() Enter the judgment if array() exists
        if (hasArray()) {
            // many lists -> many lists
            OldCount = Obtain the number of lists in array()->lists
            uint32_t oldCount = array()->count;
            // New number = oldCount + newly added
            uint32_t newCount = oldCount + addedCount;
            // Open memory according to 'newCount', type is array_t
            array_t *newArray = (array_t *)malloc(array_t: :byteSize(newCount));
            // Set the number of new arrays equal to 'newCount'
            newArray->count = newCount;
            // Set the number of old arrays equal to 'newCount'
            array()->count = newCount;
            NewArray ->lists ->lists ->lists
            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];
            // Iterate through the list in the two-dimensional pointer 'addedLists' and store it in newArray->lists and start from the beginning
            // start to store
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            // Release the original array()
            free(array());
            // Set a new newArray
            setArray(newArray);
            validate(a); }// If there are methods in the main class, first list the methods in the main class
        // If there is no method in the main class, then the class method will come in
        else if(! list && addedCount ==1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate(a); }Array_t = array_t; array_t = array_t
        // Lists of 'array_t' store address Pointers for each classified array
        else {
            // 1 list -> many lists
            // Assign the list array to oldList
            Ptr<List> oldList = list; 
            OldList has oldCount = 1
            uint32_t oldCount = oldList ? 1 : 0; 
            //新的newCount = 原有的count + 新增的count
            uint32_t newCount = oldCount + addedCount;
            // Open memory according to 'newCount', type is array_t, array()->lists is a two-dimensional array
            setArray((array_t *)malloc(array_t: :byteSize(newCount)));
            // Set the number of arrays
            array()->count = newCount;
            // Put the original list at the end of the array
            if (oldList) array()->lists[addedCount] = oldList;
            // Iterating through addedLists stores the iterated data at the beginning of the array
            for (unsigned i = 0; i < addedCount; i++)
                array()->lists[i] = addedLists[i];
            validate();
        }
    }
Copy the code

Source analysis, attachLists is a three-step process

0 list –> 1 list

  • willaddedLists[0]Is assigned tolist

1 list –> many lists

  • Calculation of the oldlistThe number of
  • Calculate the newlistNumber, newlistNumber = the original listThe number of+ additionallistThe number of
  • According to thenewCountOpen the corresponding memory, type isarray_tType, and set the arraysetArray
  • Will the originallistPut it at the end of the array, because at most one of them doesn’t need to go through the storage
  • traverseaddedListsStores the traversed data at the beginning of the array

many lists –> many lists

  • judgearray()If there is a
  • Compute the original arraylistThe number ofarray()->listsThe number of
  • The newnewCount= the originalcount+ additionalcount
  • According to thenewCountOpen the corresponding memory, type isarray_ttype
  • Set the number of new arrays equal tonewCount
  • Set the number of old arrays equal tonewCount
  • Iterating through the original arraylistStore itnewArray->listsIs placed at the end of the array
  • traverseaddedListsStores the traversed data at the beginning of the array
  • Release the originalarray()
  • Set up the newnewArray

Note: List* const * addedLists is a second-level pointer. Just like LWPerson * p = [LWPerson alloc], p is called a first-order pointer and &p is called a second-order pointer

SetArray and hasArray

    union {
        Ptr<List> list;
        uintptr_t arrayAndFlag;
    };

    bool hasArray(a) const {
        return arrayAndFlag & 1;
    }

    array_t *array(a) const {
        return (array_t *)(arrayAndFlag & ~1);
    }

    void setArray(array_t *array) {
        arrayAndFlag = (uintptr_t)array | 1;
    }

    void validate(a) {
        for (auto cursor = beginLists(), end = endLists(a); cursor ! = end; cursor++) cursor->validate(a); }Copy the code
  • setArray()methodsarrayAndFlag = (uintptr_t)array | 1arrayExclusive or1.arrayAndFlagThe first0A must have1
  • hasArray()methodsarrayAndFlag & 1. ifarrayAndFlagThe first0Who is the1, it returnsYESOtherwise returnNO
  • So just callsetArray()Method, do not release.hasArray()isYES, and up here3And the process

attachListsProcess analysis diagram

To verifyattachLists

AttachLists are not possible to bypass attachCategories. AttachCategories are not explored in detail

validationattachCategories

Create the main class LWPerson and the classification LWA

@implementation LWPerson
+(void)load{
  NSLog(@"I'm the main class LWPerson."); } - (void)sayHello{ 
  NSLog(@"sayHelloP");
} 
@end
 
@implementation LWPerson (LWA) 
+(void)load{ 
  NSLog(@"I am classification LWA"); 
}
- (void)AsayNB{
  NSLog(@"Here I come.");
}
@end
Copy the code

Run the source code for debugging

The figure shows that the loading is the method list of LWA classification, take a look at the structure of mlists

The last bit stored in mlists is the address of the list of methods classified by LWA. Mlists is a two-dimensional pointer type

mlists + ATTACH_BUFSIZ - mcountThat’s address offset and what we explored earlierd+nIt’s the same thing.mlistsIs the initial address,ATTACH_BUFSIZ - mcountThe exact number of positions

validationattachLists

Example verification to check macho + dynamic debugging for mutual verification, before the verification of how to add next read macho files, source appears _getObjc2ClassList, _getObjc2NonlazyClassList and other methods, enter the method to see the specific implementation

#define GETSECT(name, type, sectname)                                   \
    type *name(const headerType *mhdr, size_t *outCount) {              
        returngetDataSection<type>(mhdr, sectname, nil, outCount); \} \type *name(const header_info *hi, size_t *outCount) {\return getDataSection<type>(hi->mhdr(), sectname, nil, outCount); The \}// function name content type section name
// Refs ends with classes and methods that need to be fixed
GETSECT(_getObjc2SelectorRefs,        **SEL**,          "__objc_selrefs"); 

GETSECT(_getObjc2MessageRefs,         message_ref_t."__objc_msgrefs"); 

GETSECT(_getObjc2ClassRefs,           Class,           "__objc_classrefs");

GETSECT(_getObjc2SuperRefs,           Class,           "__objc_superrefs");
// Macho section equals __objc_classList List of all classes (excluding classes)
GETSECT(_getObjc2ClassList,           classref_t支那const* *,"__objc_classlist");
// The macho section is equal to the list of __objc_NLclslist lazily loaded classes
GETSECT(_getObjc2NonlazyClassList,    classref_t支那const* *,"__objc_nlclslist");
// The macho section is equal to the list of __objc_catList classes
GETSECT(_getObjc2CategoryList,        category_t* * *const* *,"__objc_catlist");
// The macho section is equal to the list of __objc_catList2 classes
GETSECT(_getObjc2CategoryList2,       category_t* * *const* *,"__objc_catlist2");
// The macho section is equal to __objc_NLcatList lazy-loaded class
GETSECT(_getObjc2NonlazyCategoryList, category_t* * *const* *,"__objc_nlcatlist");
// The macho section equals __objc_protolist protocol list
GETSECT(_getObjc2ProtocolList,        protocol_t* * *const* *,"__objc_protolist");
// The macho section equals the __objc_protolist protocol fix list
GETSECT(_getObjc2ProtocolRefs,        protocol_t *,    "__objc_protorefs");
// The macho section is equal to __objc_init_func __objc_init list of initialization methods
GETSECT(getLibobjcInitializers,       UnsignedInitializer, "__objc_init_func");
Copy the code

__objc_nlclslist is the name of the section corresponding to the Macho file (specifically, the executable file)

The names of the sections on the left of the Macho file correspond to the lists of data on the right, which should be clear in the figure

01

If rWE has a value, the extAllocIfNeeded method will definitely be called. The timing of the call to extAllocIfNeeded dynamically adds data or the data of the classification dynamically adds to the main class. Now study the situation of the classification. Enter extAllocIfNeeded method

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

Rwe does not exist to open up memory to call the extAlloc method

class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    runtimeLock.assertLocked(a);// Create memory for rWE
    auto rwe = objc::zalloc<class_rw_ext_t> (); rwe->version = (ro->flags & RO_META) ?7 : 0;
    // If the main class has methods, then the main class method list is' attachLists' 0 to many
    method_list_t *list = ro->baseMethods(a);if (list) {
        if (deepCopy) list = list->duplicate(a); rwe->methods.attachLists(&list, 1);
    }
    // If the main class has properties, then the property list of the main class is' attachLists', which is 0 to many
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }
     // If the main class has protocols, then attachLists the main class's protocol list 0 to many
    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }
    / / set rwe
    set_ro_or_rwe(rwe, ro);
    / / return rwe
    return rwe;
}
Copy the code

The source code says if there are methods in the main class, attachLists the methods, and if there are no methods in the main class, do nothing at this point. There are two situations where the main class has methods and the main class has no methods

The main class has methods, the classification has methods

Create the main classes LWPerson and LWTeacher, and create the categories LWPerson+LWB and LWPerson+LWA as follows

@implementation LWTeacher
- (void)sayHello{
    NSLog(@"sayHelloT");
}
@end

@implementation LWPerson
+(void)load{
    NSLog(@"I'm the main class LWPerson."); } - (void)sayHello{
    NSLog(@"sayHelloP");
}
@end

@implementation LWPerson (LWB)
+(void)load{
   NSLog(@"I'm classified LWB.");
}
- (void)BsayHello{
    NSLog(@"Here I come.");
}
@end

@implementation LWPerson (LWA)
+(void)load{
    NSLog(@"I am classification LWA");
}
- (void)AsayNB{
    NSLog(@"Here I come.");
}
@end
Copy the code

Run the objC source to locate the attachCategories method, because we are now looking at categories

The first break point in the diagram breaks and then enters the second break point to filter other classes. Then enter the extAllocIfNeeded method

At this point, rWE has no value, so we need to open up memory and use extAlloc

Now, list has a value, so I’m going to call attachLists, so I’m going to go to the attachLists method

  • The breakpoint to enter0right1The judgment of
  • throughlldbDebug is a list of methods for the main class

The main class has no methods, the classification has methods

Remove the sayHello method from LWPerson and repeat the debugging steps above

@implementation LWPerson
+(void)load{
    NSLog(@"I'm the main class LWPerson.");
}
@end
Copy the code

Follow the above debugging process, go to extAlloc, list = NULL, so attachLists will not be called, but to dynamically add to the main class 0 to 1 the flow flow must go in, then debug

The second parameter to attachCategories is to wrap the category into a locstamped_category_T type debugging through LLDB, where the category is LWA, and so on

The attachLists method is called, so we go to the attachLists method

What goes into the 0-to-1 flow is the list of methods in LWA, and you might wonder why the list of methods in LWA is loaded instead of the list of methods in LWB. It has to do with the order in which the classification is compiled, the order in which it is compiled

If you categorizeLWBIn the classificationLWAI’m going to compile that firstLWBIt will load first

conclusion

There are two scenarios for a 0-to-1 process

  • Main class has methods, classification has methods: based on the methods of the main class, the methods of the classification are loaded into the main class
  • The main class has no methods, but the categories have methods: the other categories are combined based on the first compiled category, and then loaded into the main class

1more

Now add the sayHello method to LWPerson and run the source code

Compile the classification LWB before the classification LWA and then the LWB will be loaded first, into the attachLists method

List is the list of methods in the main class. AddedLists holds the pointer to the classification

Array ()->lists (array()->lists (array()->lists

morerightmore

The next logical step is to enter the many-to-many process and continue debugging breakpoints

The LWA classification is loaded, and the attachLists method is used

HasArray ()=ture enters the many-to-many process, and only one method list in the secondary pointer of addedLists is classified by LWA

newArraythelistsIn the store3A list of methods, which are classifiedLWAThe list of methods, respectively, is classifiedLWBAnd the main class method list. The final compiled category is in the wholelistsThe front of the

The collocation of classes and classifications

Non-lazy loading class + Non-lazy loading class

Load process for non-lazy loading classes Load is implemented for non-lazy loading classes and LOAD is implemented for non-lazy loading classes. The lazy loading class load data through _getObjc2NonlazyClassList method from macho file access, classification of the lazy loading data loading were obtained through _getObjc2NonlazyCategoryList method from macho file

The non-lazily loaded class gets the data schematic

Diagram of non-lazy loading classification to obtain data

  • Non-lazy loading class loading process:map_images –> map_images_nolock –> _read_images –> realizeClassWithoutSwift –> methodizeClass –> attachToClass
  • Non-lazy loading classification loading process:load_images –> loadAllCategories –> load_categories_nolock –> attachCategories –> attachLists

Schematic Diagram of Log Printing

Non-lazy loading class + lazy loading class

  • Non-lazily loaded classesOr walkmap_imagesprocess
  • The lazy loading class does not goattachCategoriesWhen is the list of methods in the category loaded

machoThere is no data in the classified list, which means that it is impossible to load the classified data dynamically. So when to load the classified data

Guess: if it is not added dynamically, then it is most likely fetched at compile time, when the class is loadedroTo see if there is any classified data

roThere are not only main class methods, but also classification methods.roIs determined at compile time, meaning that the data in the lazy loading class is already merged into the main class at compile time. The classification data is also placed before the methods of the main class

Lazy loading class + lazy loading class

The print information shows that the loaded class did not go through the MAP_images process, indicating that no LWPeson is not in the non-lazy load list

Macho does not have a list of non-lazily loaded classes and a list of non-lazily loaded classes, so when are classes loaded? Add a breakpoint in realizeClassWithoutSwift to view the stack information

The stack information display is called realizeClassWithoutSwift method through the message slow lookup process, everything is so predestined, some information clearly related to each other, this is predestined

So when is the classified data loaded? At compile time, obviously. To validate the

The process of lazy loading a class is that the class is loaded the first time a message is sent, and the data of a lazy loading class is merged into ro at compile time

Lazy loading class + non-lazy loading class

In this case, the process is complicated because the number of non-lazy load classes has an impact on the overall loading process

Lazy loading class + a non-lazy loading class

This is the same as the non-lazy loading class + lazy loading class. The non-lazy loading class forces the lazy loading class to load at the same time as the non-lazy loading class. At compile time, the lazy loading class becomes the non-lazy loading class, and the data of the non-lazy loading class is merged into the main class

Macho file data display is already obvious, let’s see if the classification merge in ro

Lazy-loaded classes become non-lazy-loaded classes, and the classified data is merged into ro at compile time

Lazy loading class + multiple non-lazy loading classes

The main class load did not gomap_imagesProcess, called twiceload_categories_nolockThere are two categories, but they didn’t goattachCategoriesMethod, but walkrealizeClassWithoutSwiftLoad the main class, and then callattachCategoriesProcess. There are two issues that need to be addressed throughout the process

  • Class loading process did not goattachCategoriesMethod, so what is its flow
  • How to callrealizeClassWithoutSwiftThe process of

Macho has a list of categories and non-lazy loading categories. There are LWB categories and LWA categories, but there is no list of non-lazy loading categories

Add a breakpoint in load_categories_NOLock and run the source code

CLS if initialization is attachCategories method, if not go unattachedCategories. AddForClass method. Enter the addForClass method

void addForClass(locstamped_category_t lc, Class cls)
{
    runtimeLock.assertLocked(a);if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: found category %c%s(%s)",
                     cls->isMetaClassMaybeUnrealized()?'+' : The '-',
                     cls->nameForLogging(), lc.cat->name);
    }
    // Select * from hash table where lc is not found
    auto result = get().try_emplace(cls, lc);
    // If there is data in result.second, do not save lc to result.second
    if(! result.second) { result.first->second.append(lc); }}Copy the code
  • So let’s go to the hash table firstkeyisclsIs to findlc.lcIs a unified data type encapsulated at the bottom of the system
  • If there islcTo determinelc.secondIs there any data? If not, assign a value
  • Note that the categorization data now exists directly in the hash table and that the class is now unrelated. That’s important

Explore how to load the class in realizeClassWithoutSwift add breakpoints, run the source code

The stack information display is load_images –> prepare_load_methods –>realizeClassWithoutSwift explore the concrete implementation of prepare_load_methods

void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;

runtimeLock.assertLocked(a);// Get the non-lazily loaded list of classes from Macho
classref_t const *classlist = 
    _getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
    // Add the remapped class to the load list
    schedule_class_load(remapClass(classlist[i]));
}
// Get the list of non-lazily loaded classes from Macho
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
    category_t *cat = categorylist[i];
    Class cls = remapClass(cat->cls);
    if(! cls)continue;  // category for ignored weak-linked class
    if (cls->isSwiftStable()) {
        _objc_fatal("Swift class extensions and categories on Swift "
                    "classes are not allowed to have +load methods");
    }
    // Class loading
    realizeClassWithoutSwift(cls, nil);
    ASSERT(cls->ISA() - >isRealized());
    // Add the category to the load list for the category
    add_category_to_loadable_list(cat); }}Copy the code
  • machoGets the list of non-lazily loaded classes in
  • Adds the remapped class to theloadIn the list
  • machoTo get a list of non-lazy loading categories
  • Class
  • Adds a category to the classifiedloadIn the list

Explore the schedule_class_load

static void schedule_class_load(Class cls)
{
    if(! cls)return;
    ASSERT(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;
    // Parent class of recursion 'CLS'
    // Ensure superclass-first ordering
    schedule_class_load(cls->getSuperclass());
    // Add the class and its parent to the load table
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); `IMP`
}
Copy the code

Add_category_to_loadable_list is basically the same as add_class_to_loadable_list

{...// Omit part of the code
 // Get IMP for load method in class
 method = cls->getLoadMethod(a);// Load table of classloadable_classes[loadable_classes_used].cls = cls; loadable_classes[loadable_classes_used].method = method; loadable_classes_used++; . .// Obtain IMP for the load method in the class
 method = _category_getLoadMethod(cat);
 // Load table of classificationloadable_categories[loadable_categories_used].cat = cat; loadable_categories[loadable_categories_used].method = method; loadable_categories_used++; . }Copy the code

Add_category_to_loadable_list and add_class_to_loadable_list hold encapsulated types. This type has two variables: CLS holds the class and method holds the IMP of the load method

To explore the following process of realizeClassWithoutSwift, according to the printed message the process is realizeClassWithoutSwift –> attachToClass –> attachCategories

void attachToClass(Class cls, Class previously, int flags)
{
runtimeLock.assertLocked(a);ASSERT((flags & ATTACH_CLASS) ||
       (flags & ATTACH_METACLASS) ||
       (flags & ATTACH_CLASS_AND_METACLASS));
auto &map = get(a);// The table corresponding to key = PREVIOUSLY (CLS) in the hash table
// This table holds the data classified by the user
auto it = map.find(previously);
// If it is not equal to the last data in the table, the last data is regarded as the identification bit
if(it ! = map.end()) {
    // Get the address of the data list for the category
    category_list &list = it->second;
    if (flags & ATTACH_CLASS_AND_METACLASS) {
        // Load classification data to the metaclass class
        int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
        attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
        attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
    } else {
        // Load the class with class data
        attachCategories(cls, list.array(), list.count(), flags);
    }
    // Release table data corresponding to CLS
    map.erase(it);
}
Copy the code

Debug at attachToClass breakpoints

it ! = map.end() = map.end() = map.end() = map.end(

The classified data exists in the underlying table. When the classified data needs to be loaded into the main class, it is obtained from the table. After loading, the classified data in the corresponding table is cleared

conclusion

The class loading is very complicated. It is very complicated to write a blog for each place. Now I can only sort out the whole process. The exploration of the bottom is still continuing, which is pain and happiness