1. Review

In my previous blog, I learned a little bit about class loading, and the loading of categories is also located in the attachCategories method, so this post will explore categories!

IOS low-level exploration such as loading (two): realizeClassWithoutSwift analysis

2. Classification analysis

2.1 Classification loading route

In the last blog post, two routes of classification loading have been identified by backtracking, which are:

  • methodizeClass --> attachToClass --> attachCategories
  • load_images --> loadAllCategories --> load_categories_nolock --> attachCategories

  • realizeClassWithoutSwift

MethodizeClass is called in realizeClassWithoutSwift:

staticClass realizeClassWithoutSwift(Class cls, Class previously) { runtimeLock.assertLocked(); class_rw_t *rw; Class supercls; Class metacls; . Omit...// Attach categories
    methodizeClass(cls, previously);

    return cls;
}
Copy the code
  • methodizeClass
static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    boolisMeta = cls->isMetaClass(); auto rw = cls->data(); auto ro = rw->ro(); auto rwe = rw->ext(); . Omit...// 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... }Copy the code

Previously = nil (methodizeClass = nil) by searching the realizeClassWithoutSwift call, previously = nil (methodizeClass = nil), if will not execute

So why is previously nil? So what does this parameter mean?

It’s supposed to be something that apple engineers can easily use for tuning code, that is, a backup parameter that allows for dynamic tuning.

2.2 attachCategories

Append the list of methods, attributes, and protocols from the classification to the class. Categories in Cats are assumed to have been loaded and sorted in the order they were loaded, with the oldest category being attached first.

  • attachCategories
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
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);
    auto rwe = cls->data()->extAllocIfNeeded();
    
    const char *mangledName = cls->nonlazyMangledName();
    if (strcmp(mangledName, "LGPerson") = =0)
    {
        if(! isMeta) { printf("LGPerson.... \n"); }}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) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                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, __func__);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return! c->cache.isConstantOptimizedCache(); }); } } rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount); rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount); }Copy the code
  1. The first is to create three arrays for handling methods, properties, and protocols. The maximum storage space is64
   constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];
Copy the code
  1. And then there’s whether it’s fromfromBundleWhether,The metaclassMark, rightrweInitialize.
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    auto rwe = cls->data()->extAllocIfNeeded();
Copy the code
  1. throughforLoop to classify methods, attributes, protocols for processing, related informationattachTo the class. Gets the list of class methods if the current class is a metaclass, otherwise gets the list of instance methods:
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
Copy the code
  • methodsForMeta
method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
Copy the code

The classification list is cyclically processed, and the data is inserted in reverse order (mlists[ATTACH_BUFSIZ – ++ McOunt] = mlist). It should be noted that mLists are a two-dimensional array and the printed test results are as follows:

(lldb) p mlist
(method_list_t *) $4 = 0x00007fff80b44aa8
(lldb) p mlists
(method_list_t *[64]) $5= {[0] = 0x00007fff80849050
  [1] = 0x00007fff80848a50
  [2] = 0x00007fff205190b9
  [3] = 0x00007fff203acfca
  [4] = nil
  [5] = 0x0000000000001e00. [58] = 0x00000001003660f0
  [59] = 0x0000000100366140
  [60] = 0x00007ffeefbf5b50
  [61] = 0x00000001002f27fa
  [62] = 0x00007ffeefbf5b50
  [63] = 0x00007fff80b44aa8
}
Copy the code
  • It can be verified from the print result that the maximum storage space is yes64.
  • Put the address of the class’s method list in the last element of the array, the comparison address is exactly the same0x00007fff80b44aa8.
  1. After processing methods, properties, and protocols, the associated collection data is inserted intorweIn:
if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return! c->cache.isConstantOptimizedCache(); }); } } rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount); rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);Copy the code

It is important to note that the mlist of methods is a two-dimensional array. The sorting of classification methods is only for the sorting of the methods in each of their own classification, and it will not put all the methods of classification into a set for sorting.

2.3 rewTo deal with

In the last blog post, we mentioned that the rwe assignment comes from rw->ext() as follows:

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

Ext () is a method that belongs to the class_rw_t structure

class_rw_ext_t *ext() const {
    return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}

class_rw_ext_t *extAllocIfNeeded() {
    / / for rwe
    auto v = get_ro_or_rwe();
    if (fastpath(v.is<class_rw_ext_t *>())) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
    } else {
        / / create rwe
        return extAlloc(v.get<constclass_ro_t *>(&ro_or_rw_ext)); }}Copy the code

There are also rWE creation and retrieval methods in class_rw_t. Tracing the extAllocIfNeeded method in objC source code, it is found that the extAlloc method is called for initialization. The process inserts ro data into RWE first:

  • extAlloc
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    runtimeLock.assertLocked();

    auto rwe = objc::zalloc<class_rw_ext_t>();

    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

It can be seen from the above extAlloc method that all branch statements will call attachLists method for method, attribute and protocol processing, so now we will analyze attachLists.

2.4 attachLists

 void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;

            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            free(array());
            setArray(newArray);
            validate();
        }
        else if(! list && addedCount ==1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 
        else {
            // 1 list -> many lists
            Ptr<List> oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            for (unsigned i = 0; i < addedCount; i++) array()->lists[i] = addedLists[i]; validate(); }}Copy the code

From the breakpoint of the code, you can see that addedLists are a pointer to a two-dimensional array, as shown below:

  1. When the classification first comes in, it goes onarray()Initialize, and set the size of the array, i.eThe original classList number addedclassificationThe number of lists. At the same time, the classlistPut it in the last position, and the one-dimensional array becomes two-dimensional.
 // 1 list -> many lists
            Ptr<List> oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            for (unsigned i = 0; i < addedCount; i++)
                array()->lists[i] = addedLists[i];
            validate();
Copy the code

Add the list of categories to array() through a for loop

  1. The next time you re-enter, due toarray()It’s already initialized, so it goes toifIn the branch.
if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;

            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            free(array());
            setArray(newArray);
            validate();
        }
Copy the code

Verify that the current array() data order is the same as it was when the array() was first inserted. The class list comes first, and the classification list comes first.

At this point, malloc will create a new newArray reinitialization, insert the original array’s data in the same order into the newArray, and insert the new category list into the first location.

  1. In the previousrweCreate a one-dimensional array, then call itattachListsWhen will beroThe data is put into therweThe corresponding one-dimensional array.
else if(! list && addedCount ==1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 
Copy the code

3. Loading classes and categories

Lazy-loaded and non-lazy-loaded classes: Whether the current class implements the load method. A class that implements a load method is not lazily loaded. The classification debugging verification results are as follows:

3.1 Both classes and classes implement the load method

Both classes implement load, which is the case for non-lazy-loaded classes and non-lazy-loaded classes.

  • The main class call paths are:

Map_images ->_read_images ->realizeClassWithoutSwift->methodizeClass ->methodizeClass ->methodizeClass

MethodizeClass will sort the methods in RO, and rWE has not yet been created.

  • Classification:load_images->loadAllCategories->load_categories_nolock->attachCategories

  • Break point, break point atattachCategoriesIn the method, the printing classification method is as follows

3.2 The main class implements LOAD, but the classification does not

The main class implements load, but the class does not load, which is the case of non-lazily loaded classes and lazily loaded classes. Through breakpoint debugging, it does not go attachCategories, so when does the classification method load, we add a breakpoint in realizeClassWithoutSwift, Print out the information about RO.

At this point, the classification information is already available, which leads to the conclusion that the classification information is obtained in data().

The method call paths are:

Map_images -> map_images_NOLock ->_read_images ->realizeClassWithoutSwift->methodizeClass->attachToClass

3.3 The main class does not load, but the class implements load

There’s no load for the main class, there’s load for the classes, so lazy classes and non-lazy classes.

The console prints the list of ro methods, and you can see that the classified methods come first, that is, when inserting methods, the classified methods come before the main class.

The method call paths are:

Map_images ->map_images_nolock-> read_images ->realizeClassWithoutSwift->methodizeClass->attachToClass, The attachCategories method is not called, and in the same way that methods to verify categories are obtained from data(), methods in categories are added to data() during compilation.

3.4 Load is not implemented for the main class and classification

Neither the main class nor the class implements load, which is the case with lazy loading classes and classes. Verify that the lazy loading class gets the methods to load classes and classes from data() when the first message is sent.

Method invocation path is in turn: lookUpImpOrForward – > realizeClassMaybeSwiftMaybeRelock – > realizeClassWithoutSwift won’t call attachCategories method,

Lazily loaded classes, initialized the first time a message is sent, and methods in the class are automatically added to data()

3.5 There are multiple classes that implement the load method and some that do not

The main class load method is not implemented, but several classes, some implement load method, some do not implement load method.

  • The main classloadMethod is not implemented, execution path is:

_read_images ->realizeClassWithoutSwift->methodizeClass->attachToClass, classification method from data()

  • The main class haveloadMethod, the execution path is:

_read_images(non-lazy-loaded class)->realizeClassWithoutSwift-> load_categories_NOLock ->attachCategories. The category method is added dynamically from attachCategories.

4. To summarize

  • Lazy-loaded classesandNon-lazy-loaded classesThe difference is whether it is achieved+loadmethods
  • Class and classification methods are in their ownlistsorted
  • Try to avoid using too muchloadMethod, which consumes too much load time
  • addedListsIt’s a pointer to a two-dimensional array
  • rweNot in every class, in every classruntimeWhen the runtime adds methods, classes, protocols, and versions to a classrweoperate