IOS bottom layer exploration – class loading

Time:2021-6-1

IOS bottom Exploration Series

  • IOS bottom layer exploration – alloc & init
  • Exploration of IOS bottom layer – calloc and ISA
  • IOS bottom layer exploration – class
  • IOS bottom layer exploration cache_ t
  • IOS bottom layer exploration method
  • IOS bottom layer exploration – message search
  • IOS bottom layer exploration – message forwarding
  • IOS bottom layer exploration – application loading
  • IOS bottom layer exploration – class loading

1、 Application loading review

In the last chapter, we have a preliminary understanding of the loading of the application, we know

  • system callexec()It will be our applicationmappingTo a new address space
  • And then throughdyldLoad, link and initialize the main program and various dynamic libraries that the main program depends on
  • At the end of the dayinitializeMainExecutableMethod after a series of initialization callsnotifySingleFunction, which executes aload_imagesCallback for
  • And then in thedoModinitFuntionsFunction is called internally__attribute__((constructor))Ofcfunction
  • thendyldReturns the entry function of the main program and starts to enter the main programmainfunction

staymainFunction execution, actuallydyldIt is also initialized in the processlibSystem, andlibSystemIt will be initialized againlibDispatch, inlibDispatchThere will be another step in the initialization method_os_object_init, in_os_object_initIt’s going to start inside_objc_init. And for_objc_initWe need to continue to explore, because there will be a series of important work such as class loading.

IOS bottom layer exploration - class loading

2、 Explore_objc_init

First camelibObjcSource code_objc_initMethod, you can directly add a symbolic breakpoint_objc_initOr global search keywords come here:

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

We then analyze it

IOS bottom layer exploration - class loading

  • Judge whether it has been initialized. If it has been initialized, return it directly.

2.1 environ_init

Then cameenviron_initMethods: internal data were collected

IOS bottom layer exploration - class loading

As we can see, here is mainly the read effectRuntimeIf necessary, you can also print the help prompt of environment variables.

We can test it on the terminal and input it directlyexport OBJC_HELP=1:

IOS bottom layer exploration - class loading

You can see that the corresponding contents of different environment variables are printed out.

2.2 tls_init

Then cametls_initMethods: internal data were collected

void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
    _objc_pthread_key = TLS_DIRECT_KEY;
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}

This is about threadskeyFor example, the destructor of data per thread.

2.3 static_init

Then camestatic_initMethods: internal data were collected

/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors, 
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
}

It will run hereC++In thedyldBefore calling our static constructor,libcWill call_objc_initSo here we have to do it ourselves, and only the built-in system will be initializedC++Static constructors, written in our own code, will not be initialized here.

2.4 lock_init

Then camelock_initMethods: internal data were collected

void lock_init(void)
{
}

As we can see, this is an empty implementation. in other wordsobjcThe lock is completely adoptedC++That set of lock logic.

2.5 exception_init

Then cameexception_initMethods: internal data were collected

/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

Here is initializationlibobjcIn our exception handling system, all the exceptions triggered by our program will come to:

IOS bottom layer exploration - class loading

We can see that_objc_terminate  Is an unhandled callback function. Its internal logic is as follows:

  • Check for an active exception
  • If it is an active exception, check if it isOC  Exception thrown
  • If it isOC  Exception thrown, calluncaught_handeler  Callback function pointer
  • If notOC  Exception thrown, continueC++  Terminate operation

2.6 _dyld_objc_notify_register

Next, let’s focus on today’s exploration_dyld_objc_notify_register , Let’s take a look at its definition

IOS bottom layer exploration - class loading

Note: for reference onlyobjc  Run time use
Whenobjc  The image isMappedUnmoppedandInitializedThe registered callback function will be called.
This method isdlyd  Once the method is called, the result will be returned as a parameter of the function. For example, when allimages  as well as section  byobjc-image-info  It will be called back after being loadedmapped  method.
load  Method will also be called in this method.

_dyld_objc_notify_register  Three parameters of the methodmap_images 、 load_images 、 unmap_image  In fact, they are function pointers

IOS bottom layer exploration - class loading

These three function pointers are in thedyld  In the callback, we open itdyld  The source code can be explored, we directly search_dyld_objc_notify_register :

IOS bottom layer exploration - class loading

Then camedyld  OfregisterObjCNotifiers  Methods: internal data were collected

IOS bottom layer exploration - class loading

IOS bottom layer exploration - class loading

Through the above two screenshots of the content of the description in theregisterObjCNotifiers  Inside,libObjc  These three function pointers are passed indyld  Stored in a local static variable. In other words, whether the final function pointer can be called depends on these three static variables:

  • sNotifyObjCMapped 
  • sNotifyObjCInit 
  • sNotifyObjCUnmapped 

We note thatregisterObjCNotifiers  Oftry-catch  In a sentencetry  The branch notes are as follows:

call ‘mapped’ function with all images mapped so far
callmapped  Function to map all images

So that is to saynotifyBatchPartial  It will call the real function pointer. Let’s go inside this method:

IOS bottom layer exploration - class loading

We can see that in thenotifyBatchPartial  Method, here’s the comment:

Tell objc about new imagesobjc  The image has been mapped

This is where the arrow pointssNotifyObjCMapped  Where the function pointer is actually called.

It is not enough to find out how the three function pointers are called. Next, we need to go deep into the interior of each function to see what has been done.

3、 Explore map_ images

First of allmap_images , We come to its realization:

/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}C

Process the given images which are being mapped in by dyld.
Calls ABI-agnostic code after taking ABI-specific locks.

Processing bydyld  The given image of the map
Get specific toABI  After locking, call andABI  Irrelevant code.

It’s going down heremap_images_nolock 

map_images_nolock  The internal code is very lengthy. After our analysis, the previous work is basically the extraction and statistics of image file information, so we can locate to the final location_read_images :

IOS bottom layer exploration - class loading

Enter here_read_images  What’s your conditionhCount  Greater than 0,hCount  It meansMach-O  in  header  The number of

OK, our protagonist is on the stage,_read_images  andlookupImpOrForward  It can be said that we studyRuntime  andiOS  There are two very important concepts in the bottom layer,lookUpImpOrForward  It’s been explored. The rest_read_images  We can’t fall behind either.

IOS bottom layer exploration - class loading

3.1 _ read_ Image definition

Perform initial processing of the headers in the linked list beginning with headerList. 
fromheaderList  At the beginning, we have linked theMach-O  The header in the mirror table is initialized

We can see that the whole_read_images  There are nearly 400 lines of code. We might as well fold up the branch code in it, and then give an overview:

IOS bottom layer exploration - class loading

IOS bottom layer exploration - class loading

IOS bottom layer exploration - class loading

By folding the code and printing the prompt information in the log, we can roughly_read_images  It is divided into the following processes:

3.2 _ read_ Specific process of images


Doneonce process
**
We start with the first branchdoneOnce  At first, as the name suggests, the term will only be executed once

IOS bottom layer exploration - class loading

  • Through macroSUPPORT_NONPOINTER_ISA  Determine whether memory optimization is currently supportedisa 

    • If supported, this optimization needs to be disabled under certain conditions
  • Through macroSUPPORT_INDEXED_ISA  Determine whether the class is currently stored in  isa  As class table index

    • If so, recursively traverse all theMach-O  And judge if it isSwift 3.0  Before the code, you need to disable theisa  Memory optimization based on FPGA

IOS bottom layer exploration - class loading
 

  • Through macroTARGET_OS_OSX  Judge whether it ismacOS  execution environment
  • judgemacOS  If less than10.11  Then explainapp  It’s too old. It needs to be disablednon-pointer isa 
  • And then go through all of themMach-O  The head, judge if there is__DATA__,__objc_rawisa  If a segment exists, it is disablednon-pointer isa , Because a lot of new onesapp  This kind of judgment operation is required when loading old extensions.

IOS bottom layer exploration - class loading

Pre optimized classes are not added to thegdb_objc_realized_classes  In this hash table,gdb_objc_realized_classes  The loading factor of the hash table is 0.75, which is a critical value for efficient expansion.

  • Load all classes to classgdb_objc_realized_classes  From the table

Let’s look at the definition of this table

IOS bottom layer exploration - class loading

// 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.

This is a misnomer: GDB_ objc_ realized_ The classes table actually stores classes that are not in thedyld  Share the named classes in the cache, no matter whether they are implemented or not

exceptgdb_objc_realized_classes  In addition to the table, there is a tableallocatedClasses :

IOS bottom layer exploration - class loading

  • adoptobjc_allocateClassPair  After the development of the class and metaclass storage table (that is, the need toalloc )

actuallygdb_objc_realized_classes  yesallocatedClasses  It is a relationship of inclusion. One is a general table of classes, and the other is a table of classes that have opened up memory,


Discover classes process

IOS bottom layer exploration - class loading

Discover classes. Fix up unresolved future classes. Mark bundle classes.
Class found. Fixed unresolvedfuture  Class, tagbundle  Class.

  • Pass first_getObjc2ClassList  To get all the classes, we canMachOView  To verify:

IOS bottom layer exploration - class loading

  • And then go through all of themMach-O  Ofheader  Part, and then throughmustReadClasses  To determine which conditions can skip the read class step
  • readheader  Is itBundle 
  • readheader  Is it onPre optimization
  • ergodic_getObjc2ClassList  Take out all the classes

    • adoptreadClassTo read class information
    • Judge if not equal andreadClass  If the result is not empty, you need to open up memory for the class again

Fix up reapplied classes process

IOS bottom layer exploration - class loading

Fix remapping class
Class tables and non lazy loaded class tables are not remapped (that is_objc_classlist)
Due to message forwarding, the class reference and the parent class reference are remapped (that is, the_objc_classrefs)

**

  • adoptnoClassesRemapped  Method to determine whether there is a class reference(_objc_classrefs)Remapping is required

    • Traverse if necessaryEACH_HEADER 
    • adopt_getObjc2ClassRefs  and_getObjc2SuperRefs  Take out the current traversal to theMach-O  Class reference and parent class reference, and then callremapClassRef  Remapping   

IOS bottom layer exploration - class loading


Fix up @ selector references process

IOS bottom layer exploration - class loading

correctSEL  quote

  • Add one before operationselLock  lock
  • And then traverse itEACH_HEADER 

    • If it’s onPre optimization, continue to nextMach-O 
    • adopt_getObjc2SelectorRefs  Get everythingSEL  quote
    • And then to all of themSEL  Reference callsel_registerNameNoLock  Register

In other words, the main purpose of this process is to registerSEL , Where we registered actually happened:__sel_registerName , This function if you often playRuntime  Certainly not strange:

IOS bottom layer exploration - class loading

Let’s make a brief analysis__sel_registerName  The process of the method is as follows

  • Judge whether to lock or not
  • Ifsel  If it is empty, an emptySEL 
  • frombuiltins  Search to see if it has been registered. If it is found, return the result directly
  • fromnamedSelectors  Hash table query, found to return the result
  • IfnamedSelectors  If it is not initialized, create the hash table
  • If none of the above processes are found, you need to callsel_alloc  Let’s create itSEL , Then put the newly createdSEL  Insert the hash table to fill the cache

Fix up old objc_ msgSend_ Fix up call sites process

IOS bottom layer exploration - class loading

Fix oldobjc_msgSend_fixup  call

**
The premise of this process isFIXUP  It’s turned on.

  • It’s the same old routine, ergodicEACH_HEADER 

    • adopt_getObjc2MessageRefs  Method to get theMach-O  All message references of the mirror
    • Then traverses these message references and calls them.fixupMessageRef  Make corrections

Discover protocols process

IOS bottom layer exploration - class loading

Discover the protocol and modify the protocol reference

**


Fix up @ protocol references process

IOS bottom layer exploration - class loading

Remapping all protocols

**


Realize non lazy classes process

IOS bottom layer exploration - class loading

initializationNon lazy loading class(**+load**  Methods and static examples)


Realize newly resolved future classes process

IOS bottom layer exploration - class loading

Initialize the newly resolvedfuture  class

**


Discover categories process

IOS bottom layer exploration - class loading

Handle all categories, including classes and metaclasses

**


Come here,_read_images  We can create a new file to remove some interfering information and only keep the core logic, which makes the analysis more intuitive from a macro perspective

IOS bottom layer exploration - class loading

Q & a link
Q: dyld  The main logic is to load the library, that is, the image file, but how to read it after loading?
A: _read_images  It’s the real place to read

Q: SEL  When will the method number be loaded?
A: _read_images

3.3 read_ Class analysis

We explored_read_images  Next, let’s focus on the topic of this article-Class loading
Since it’s the loading of classes, the contents in the class structure we explored above will reappear one by one.
So we might as well debug the breakpoint directly. Let’s skip other interference information and focus on class loading.

  • According to the results of our exploration in the previous section,doneOnce  Two hash tables will be created in the process, and the loading of classes is not involved, so we skip
  • We come to the second process-  Class processing


We set a breakpoint at the position shown in the figure below:

IOS bottom layer exploration - class loading
**
As shown in the figure above, fromclassList  They were taken out of the hospitalcls  It’s just a memory address that we try to passLLDB  Printingcls  Ofclas_rw_t :

IOS bottom layer exploration - class loading

You can see thatcls  The properties, methods, protocols and class names of are empty, which indicates that the class has not been loaded. Let’s focus on thisread_class  Function, let’s enter its internal implementation. After browsing, we will locate the code as shown in the following figure:

IOS bottom layer exploration - class loading

It seems that the information of the class has been loaded here. In order to verify our conjecture, we can debug the breakpoint directly, but we find that the breakpoint can’t come in at all. The reason is the judgment statement here

if (Class newCls = popFutureNamedClass(mangledName))

Determine whether the class name of the class currently passed in hasfuture  Class implementation, but we have just printed, the class name is empty, so certainly will not execute here. We went on down:

IOS bottom layer exploration - class loading

  • Addnamedclass is actually acls  Insert intogdb_objc_realized_classes  surface  

IOS bottom layer exploration - class loading

  • Inside the addclasstableentry, thecls  Insert intoallocatedClasses  surface

IOS bottom layer exploration - class loading

End of analysisread_class , Let’s go back_read_images  method

IOS bottom layer exploration - class loading

We can see thatread_class  ReturnednewCls  Will make a judgment, judgment and inputread_class  previouscls  Whether it is equal or not, andread_class  There is only one place inside to change the content of the class, but we just tested that it can’t get in, so thisif  We can skip the contents, that is to sayresolvedFutureClasses  We can skip over all the contents of.

To sum upreadClass :

  • Determine whether it is a class to be post processed

    • If so, take out the post-processing class and read thedata()  Class settingsro/rw 
  • addNamedClass   Insert master table
  • Addclasstableentry inserts the table of the class with memory opened

3.4 realizeclasswithoutswift analysis

Through analysisread_class , We can see that the class has been registered in two hash tables, so now all the time is ripe. But we still have to be a little bit more like thatFix up remapped classes 、 Fix up @selector references 、 fix up old objc_msgSend_fixup call sites 、 Discover protocols. Fix up protocol refs 、 Fix up @protocol references , Because our focus is on class loading, we finally arrivedRealize non-lazy classes (for +load methods and static instances) , After omitting irrelevant information, we can see our
leadrealizeClassWithoutSwift  Here we go:

IOS bottom layer exploration - class loading

From the name of the method and the method comment, we can see that,realizeClassWithoutSwift  It is the first initialization operation of the class, including the allocation of read and write data, which is what we often sayrw , But nothing will happenSwift  End initialization. Let’s focus directly on the following code:

IOS bottom layer exploration - class loading

  • adoptcalloc  Open up memory space and return to a new onerw 
  • holdcls  Take it outro  Assign a value to thisrw 
  • takerw  Set tocls  On the body

So is it hererw  It’s worth it. Let’s goLLDB  Printing Dafa starts:

IOS bottom layer exploration - class loading

It can be clearly seen that at this timerw  If it’s still empty, it means that it’s just rightrw  It is initialized, but the methods, properties and protocols are not added.

We went on down:

IOS bottom layer exploration - class loading

You can see that both the parent and metaclass call recursivelyrealizeClassWithoutSwift  To initialize the respectiverw 。 Why do you need to load classes and metaclasses in the class loading operation? Recall the class structure, the answer is very simple, to ensure thatsuperclass  andisa  In other words, the integrity of classes is guaranteed,

IOS bottom layer exploration - class loading

The above screenshot is the best proof that the initialized parent class and metaclass are assigned to the classsuperclass  andisa  above.

IOS bottom layer exploration - class loading

Next, we can see that we should not only associate the parent class with the class, but also let the parent class know the existence of the child class.

The last line of code ismethodizeClass(cls) , The comment showsattach categories , Attach classification to class? Let’s go inside and find out.

ExploringmethodizeClass  First, let’s summarizerealizeClassWithoutSwift :

  • readclass  Ofdata() 
  • ro/rw  assignment
  • Parent class and metaclass implementation

    • supercls = realizeClassWithoutSwift(remapClass(cls->superclass))
    • metacls = realizeClassWithoutSwift(remapClass(cls->ISA()))
  • The relationship between parent class and metaclass

    • cls->superclass = supercls
    • cls->initClassIsa(metacls)
  • Links the current class to the list of subclasses of its parent class   addSubclass(supercls, cls)

3.5 methodizeclass analysis

IOS bottom layer exploration - class loading

Modify the method list, protocol list and property list of the class
additionalcategory  Come to the class

Let’s go straight down:

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }
  • fromro  Take out fromMethod listAttach torw  upper
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }
  • fromro  Take out fromProperty listAttach torw  upper
    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }
  • fromroTake out fromProtocol listAttach torwupper
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);
  • fromcls  Take the non attached classification out of the database for additional operation

As we can see, there is an operation calledattachLists , Why can methods, properties, and protocols call this method?

IOS bottom layer exploration - class loading

IOS bottom layer exploration - class loading

IOS bottom layer exploration - class loading

We can see that the data structure of methods, properties and protocols is a two-dimensional array, and we can go deep into itattachLists  Method internal implementation:

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

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;//10
            uint32_t newCount = oldCount + addedCount;//4
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;// 10+4
   
            memmove(array()->lists + addedCount, array()->lists,
                    oldCount * sizeof(array()->lists[0]));
            
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            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;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }
  • Judge whether the quantity to be added is 0. If it is 0, return it directly
  • Judgement of current callattachLists  Oflist_array_tt  A two-dimensional array has multiple one-dimensional arrays

    • If yes, it means yesMany to manyThe relationship between
    • It’s going through hererealloc  Reallocate the container to the original size plus the new size
    • And then throughmemmove  Move the original data to the end of the container
    • Finally, copy the new data to the beginning of the container
  • If calledattachListsOflist_array_ttIf the 2D array is empty and the number of new sizes is 1, theaddedList  First of alllist  return
  • If current callsattachListsOflist_array_ttA two-dimensional array has only one one-dimensional array

    • If yes, it means yesOne to manyThe relationship between
    • It’s going through herereallocReallocate the container to the original size plus the new size
    • Because there was only one one-dimensional array, it was assigned to the new array directlyArray  The last position of
    • Then copy the new data to the beginning of the container

4、 Explore load_ images

We went on to explore_dyld_objc_notify_register  The second parameter ofload_images , When is the function pointer called? Similarly, we will continue to call it in thedyld  Search the corresponding function pointer in the source codesNotifyObjCInit :

IOS bottom layer exploration - class loading

As you can see, in thenotifySingle  Inside the method,sNotifyObjCInit  Function pointer called. Explore based on our last articledyld  You can see from the bottom,_load_images  It should be for each one loaded inMach-O  The image is called recursively once.

Here we arelibObjc  In the source codeload_images  Here is the definition:

IOS bottom layer exploration - class loading 

Processing bydyldIn the given image of the map+load  method

  • Judge whether there isload  Method, if not, returns directly
  • searchload  Method, the specific implementation throughprepare_load_methods 
  • callload  Method, the specific implementation throughcall_load_methods 

four point one   prepare_ load_ Methods analysis

From this method name, we guess what we should do here isload  Method, let’s analyze the source code

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

    runtimeLock.assertLocked();

    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t **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");
        }
        realizeClassWithoutSwift(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed 
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

/***********************************************************************
* add_class_to_loadable_list
* Class cls has just become connected. Schedule it for +load if
* it implements a +load method.
**********************************************************************/
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}
  • First of all_getObjc2NonlazyClassList  Get a list of all the classes that have been loaded in
  • And then throughschedule_class_load  Traverse these classes

    • Recursively call theload  Method to ensure that theload  The order of methods is in front of subclasses
    • adoptadd_class_to_loadable_list , Classload  Methods existloadable_classes  inside
    • IOS bottom layer exploration - class loading
  • completeschedule_class_load  After that, it was passed_getObjc2NonlazyCategoryList  Take out all the classified data
  • Then traverse the categories

    • adoptrealizeClassWithoutSwift  To prevent the class from not being initialized. If it is already initialized, it will not be affected
    • adoptadd_category_to_loadable_list , Loading data in classificationload  Method toloadable_categories  inside
    • IOS bottom layer exploration - class loading

four point two   call_ load_ Methods analysis

We can know by namecall_load_methods  It should beload  Where the method is called. Let’s look at the source code directly

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

call_load_methods
Call the class and all pending+load  method
In class+load  Methods are called first by the parent class
In the parent class+load  After that, the classified+load  method

  • adoptobjc_autoreleasePoolPush  Stack an auto release pool
  • do-while  Cycle starts

    • Loop call class+load  Method until it cannot be found
    • Call the+load  method
  • adoptobjc_autoreleasePoolPop  Stack an auto release pool

5、 Summary

So far,_objc_init  and_dyld_objc_notify_register  We have finished the analysis, and we have a more detailed understanding of class loading.iOS  It’s really boring to explore the bottom layer sometimes, but if you can find an efficient method and clear the direction of your exploration, you will let yourself re-examine this technology from a macro perspective. Yes, technology is just a tool. We can’t be kidnapped by technology. We have to explore it with a definite aim so that we can get twice the result with half the effort.

Recommended Today

Implementation example of go operation etcd

etcdIt is an open-source, distributed key value pair data storage system, which provides shared configuration, service registration and discovery. This paper mainly introduces the installation and use of etcd. Etcdetcd introduction etcdIt is an open source and highly available distributed key value storage system developed with go language, which can be used to configure sharing […]