preface

The _dyLD_OBJC_notify_register method is used to register notifications in _objc_init. It has three parameters: Map_images, load_images, and unmap_image.

_objc_init

void _objc_init(void) {
    // Determine the initialization flag
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // Initialization of environment variables
    environ_init(a);// About thread key bindings
    tls_init(a);// global static C++ constructor call
    static_init(a);// Initialize the runtime environment
    runtime_init(a);// Initialize the exception handling system.
    exception_init(a);#if __OBJC2__
    // Initialization of the cache class
    cache_t: :init(a);#endif
    // Start the callback mechanism.
    _imp_implementationWithBlock_init();
    // Register dyLD notifications with the second parameter load_images
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    // Complete the registration callback of notification DYLD
    didCallDyldNotifyRegister = true;
#endif
}
Copy the code
  • environ_init(): Initialization of environment variables, mainly affecting the runtime environment variables, through which to help debugging.
  • tls_init(): bindings for thread keys, such as thread data constructors.
  • static_init(): global static C++ constructor call. This is just calling objC itself, before the DYLD call, not the DYLD call.
  • runtime_init(): Runtime Initialization of the runtime environment. It is mainly the creation of two tables, unattachedCategories and allocatedClasses
  • exception_init(): initializes libobJC’s exception handling system.
  • cache_t::init(): Initialization of the cache class.
  • _imp_implementationWithBlock_init(): Enables the callback mechanism. Normally this doesn’t do much, because all initializations are lazy. But for some processes, such as QtWebEngineProcess, the Trampolines library is initialized.
  • _dyld_objc_notify_register(&map_images, load_images, unmap_image): Load image resources, classes, and categories.

environ_init

void environ_init(void)
{
    // Prints OBJC_HELP and OBJC_PRINT_OPTIONS outputs.
    if (PrintHelp  ||  PrintOptions) {
        // Prints all options in the Settings list
        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];            
            if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
            if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env); }}}Copy the code

Print the system-provided environment variables with OBJC_HELP and OBJC_PRINT_OPTIONS. There are several ways to get environment variables.

Getting environment variables

  • Direct way

Check the Settings source:

const option_t Settings[] = {
#define OPTION(var, env, help) option_t{&var, #env, help, strlen(#env)}, 
#include "objc-env.h"
#undef OPTION
};
Copy the code

Settings[] from objc-env.h. In objc source code, search objc-env.h directly:

  • Source code debugging

Modify the core code as follows:

void environ_init(void)
{
    for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
        const option_t *opt = &Settings[i];            
        _objc_inform("%s: %s", opt->env, opt->help);
        _objc_inform("%s is set", opt->env); }}Copy the code

Print the following:

  • Terminal way
export OBJC_HELP=1
Copy the code

Print the following:

Use of environment variables

Set environment variables such as OBJC_PRINT_IMAGES, OBJC_PRINT_LOAD_METHODS, and OBJC_DISABLE_NONPOINTER_ISA.

Enable OBJC_PRINT_IMAGES to print:

You can view images that have been loaded

Enable OBJC_PRINT_LOAD_METHODS to print:

Look at all the implemented load() methods.

Enable OBJC_DISABLE_NONPOINTER_ISA printing:

(lldb) x/4gx subObj
0x1012042a0: 0x0000000100008248 0x0000000000000000
0x1012042b0: 0x0000000780880000 0x00000001004c6228
(lldb) p/t 0x0000000100008248
(long) $1 = 0b0000000000000000000000000000000100000000000000001000001001001000
Copy the code

Disable OBJC_DISABLE_NONPOINTER_ISA print:

(lldb) x/4gx subObj
0x101304a80: 0x011d800100008249 0x0000000000000000
0x101304a90: 0x746e6f43534e5b2d 0x6f79616c206c6f72
(lldb) p/t 0x011d800100008249
(long) $1 = 0b0000000100011101100000000000000100000000000000001000001001001001
Copy the code

The last bit of isa, which is 0 on and 1 off, isa pure pointer (0 isa pure pointer).

tls_init

void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
    // Read the thread Key
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    // Set the thread key
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
Copy the code

Thread key initialization:

  • Whether to supportTHREAD_KEYS, if supported, according toTLS_DIRECT_KEYRead the threadkey.
  • If not, create a thread directlykey.

static_init

static void static_init(a)
{
    // Read different sections in Mach-o according to SECTION_TYPE.
    size_t count;
    // load __objc_init_func, which corresponds to (__DATA, __mod_init_func) in MachO.
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        // Memory translation load
        inits[i]();
    }
    
    // load the __objc_init_offs corresponding to the MachO file (__TEXT, __init_offsets).
    auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        // Memory translation load
        UnsignedInitializer init(offsets[i]);
        init();
    }
}

/ / getLibobjcInitializers definition
GETSECT(getLibobjcInitializers,       UnsignedInitializer, "__objc_init_func");

/ / getLibobjcInitializerOffsets definition
uint32_t *getLibobjcInitializerOffsets(const headerType *mhdr, size_t *outCount) {
    unsigned long byteCount = 0;
    uint32_t *offsets = (uint32_t *)getsectiondata(mhdr, "__TEXT"."__objc_init_offs", &byteCount);
    if (outCount) *outCount = byteCount / sizeof(uint32_t);
    return offsets;
}
Copy the code
  • The global staticC++Call to the constructor of. indyldcalldoModInitFunctionsBefore the static constructor,objcCall your own constructor. Because at this timeobjcInitialization of, yeahsectionI’m making a substitution, so I’m going todyldIt’s not going to call this one.
  • Internally there are two ways (frommachoFile to readc++Constructor) :
    • through__DATA, __mod_init_func
    • through__TEXT, __init_offsets

validation

The __objc_init_func function is executed.

extension

Dosect (markgc.cpp) : macho file name and method do not match because of dosect (markgC.cpp) :

  • __mod_init_funcWas modified to be called__objc_init_func.SECTION_TYPEforS_MOD_INIT_FUNC_POINTERS (0 x9)
  • __init_offsetsWas modified to be called__objc_init_offs.SECTION_TYPEforS_INIT_FUNC_OFFSETS (0 x16)
  • __mod_term_funcWas modified to be called__objc_term_func.SECTION_TYPEforS_MOD_TERM_FUNC_POINTERS (0 xa)

The macro definition can be found in the #import

header.

#define S_MOD_INIT_FUNC_POINTERS 0x9 /* section with only function pointers for initialization*/
#define S_MOD_TERM_FUNC_POINTERS 0xa /* section with only function pointers for termination */
#define S_INIT_FUNC_OFFSETS      0x16 /* 32-bit offsets to initializers */
Copy the code

runtime_init

Runtime environment initialization, mainly unattachedCategories, allocatedClasses two tables initialization.

void runtime_init(void)
{
    // Unattached category table
    objc::unattachedCategories.init(32);
    // Allocated class list containing all classes and metaclass
    objc::allocatedClasses.init(a); }// Initialize the table method
init() {
    template <typename. Ts>void init(Ts &&... Args) {
        new (_storage) Type(std::forward<Ts>(Args)...) ; }Type &get(a) {
        return *reinterpret_cast<Type *>(_storage); }}Copy the code

It will be used later in the loading of classes and categories.

exception_init

void exception_init(void)
{
    // Set the exception catch function
    old_terminate = std::set_terminate(&_objc_terminate);
}

static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
    if (PrintExceptions) {
        _objc_inform("EXCEPTIONS: terminating");
    }
    The current c++ exception type does not exist
    if (! __cxa_current_exception_type()) {
        // c++ terminates the callback
        (*old_terminate)();
    } else {
        // If it is the current exception type, determine if it is an objC exception.
        @try {
            // Roll back the scope to recapture
            __cxa_rethrow();
        } @catch (id e) {
            // If it is an objC object, the error callback is handled
            (*uncaught_handler)((id)e);
            // c++ terminates the callback
            (*old_terminate)();
        } @catch(...). {// if it is not an objc object, execute the c++ termination callback(*old_terminate)(); }}}Copy the code

Exception_init will mount an exception-handling callback, uncaught_handler, which may be used to intercept the exception crash message.

uncaught_handler

static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;

objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    // Sets the exception handling callback for uncaught objC objects
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    // Return the callback
    return result;
}
Copy the code

Exception catching cases

The uncaught_handler function can be used to encapsulate the exception catching class:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Register an exception listener
    [ZLCrashCapturer registerCrashCapture];
    return YES;
}
Copy the code

Registered in class didFinishLaunchingWithOptions realize exception handling.

+ (void)registerCrashCapture {
    NSSetUncaughtExceptionHandler(&ZLUncaughtException);
}

// Mount the callback function
void ZLUncaughtException(NSException *exception) {
    // Save the crash information locally
    saveLocal();
    // Callback processing
    if(handler) { handler(exception); }}Copy the code

By registering callback functions, ZLUncaughtException is called in real time when an exception occurs. In this way, exception information is recorded and callback is performed.

Note:

  • This method can only captureOc objectThe exception.
  • objc_setUncaughtExceptionHandlerCorresponding upper layer methodNSSetUncaughtExceptionHandler.
  • The custom ofMonitoring methodAs far as possible ondidFinishLaunchingWithOptionsTo prevent otherSDKCoverage. Too muchSDKcallNSSetUncaughtExceptionHandlerSometimes there is confusion, and the performance is capturedcrashThere’s no sign table.

cache_t::init

Initialization of the cache class

#ifTARGET_OS_SIMULATOR || defined(__i386__) || defined(__arm__) || ! TARGET_OS_MAC
#   define HAVE_TASK_RESTARTABLE_RANGES 0
#else
#   define HAVE_TASK_RESTARTABLE_RANGES 1
#endif

void cache_t::init(a)
{
#if HAVE_TASK_RESTARTABLE_RANGES
    mach_msg_type_number_t count = 0;
    kern_return_t kr;
    // Get the location of the restart table
    while (objc_restartableRanges[count].location) {
        count++;
    }
    // Restart table location registration
    kr = task_restartable_ranges_register(mach_task_self(),
                                          objc_restartableRanges, count);
    if (kr == KERN_SUCCESS) return;
#endif
}
Copy the code

_imp_implementationWithBlock_init

Start the callback mechanism. Normally this doesn’t do much, because all initializations are lazy. However, for some processes, such as QtWebEngineProcess or Steam Helper, the Trampolines library is initialized.

void _imp_implementationWithBlock_init(void) {
#if TARGET_OS_OSX
    if (__progname &&
        (strcmp(__progname, "QtWebEngineProcess") = =0 ||
         strcmp(__progname, "Steam Helper") = =0)) {
        Trampolines.Initialize(a); }#endif
}
Copy the code

_dyld_objc_notify_register

_dyld_objc_notify_register(&map_images, load_images, unmap_image);
Copy the code

The _DYLD_OBJC_notify_register registers callbacks to map_images, load_images, and unmap_image. The implementation is to call the notifySingle when the main dyLD initialization program is complete.

  • map_images: Manages all symbols in files and dynamic libraries (class,Protocol,selector,category).
  • load_images: Load executionloadmethods
  • unmap_image: Releases class-related resources.

Map_images is a pointer copy and load_images is a value pass. Because map_images needs to synchronize changes, it is very important to map the image file method to avoid the possibility of confusion. Load_images is a simple call to load that doesn’t need to be synchronized.

Map_images analysis

void map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    / / create a lock
    mutex_locker_t lock(runtimeLock);
    / / call map_images_nolock
    return map_images_nolock(count, paths, mhdrs);
}
Copy the code

map_images_nolock

The map_images_NOLock core code is _read_images:

void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    if (hCount > 0) {
        // Read the image file_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses); }}Copy the code

_read_images Process analysis

_read_images are all symbols (class, protocols, selector, categories) in management files and in dynamic libraries

right_read_imagesFunction source code for code block collapsed after each foundlogIllustrate the role of the code fast. The main steps are as follows:

  • Condition control for a load. (doneOnce)
  • Fixed precompile phase@selectorThe chaotic problem of
  • Wrong messy class handling
  • Fixed remapping some classes that were not loaded by the image file
  • Fix some messages
  • When a class has a protocol:readProtocolLoading protocol
  • Fix protocols that were not loaded
  • Classification processing (Focus on)
  • Class loading handling (Focus on)
  • Optimization of classes that are not being processed

doneOnce

if(! doneOnce) { doneOnce = YES;// Initializes obfuscation handling for small object types
    initializeTaggedPointerObfuscator(a);// Get the amount of memory allocated, which is the reverse of the load factor of 3/4.
    // Total capacity of totalClasses, unoptimizedTotalClasses to take up space.
    int namedClassesSize = (isPreoptimized()? unoptimizedTotalClasses : totalClasses) *4 / 3;
    // Create a total table, including the two tables created in runtime_init, containing the relationship
    gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
}
Copy the code
  • taggedPointerconfusion
  • By load factorload factorCalculate the total table size
  • Create a total table containingruntime_initTo create theunattachedCategoriesandallocatedClassestable
  • Total tablegdb_objc_realized_classes, whether the class is instantiated or not.

Conclusion:doneOnceCreate a total table (executed only once).

unfixedSelectors

mutex_locker_t lock(selLock);
for (EACH_HEADER) {
    if (hi->hasPreoptimizedSelectors()) continue;
    bool isBundle = hi->isBundle(a);// Read all sels from macho
    SEL *sels = _getObjc2SelectorRefs(hi, &count);
    UnfixedSelectors += count;
    for (i = 0; i < count; i++) {
        const char *name = sel_cname(sels[i]);
        // Get sel from dyld
        SEL sel = sel_registerNameNoLock(name, isBundle);
        if(sels[i] ! = sel) {// Address comparison, address repairsels[i] = sel; }}}Copy the code
  • selIs in thedyldThe load,sels[i]frommachoRead out, frommachoThe read address may be jumbled.
  • sels[i] = selIs to overwrite the address, because indyldLoaded outselThe address is valid.
  • To find theselCall path to:sel_registerNameNoLock__sel_registerNamesearch_builtins_dyld_get_objc_selector. clearlyselby_dyld_get_objc_selectorTo obtain.

Conclusion:unfixedSelectorsMain action repairselAddress confusion problem.

At sels[I] = sel interrupt point, verify:

discover classes

for (EACH_HEADER) {
    // Read ClassList from macho
    classref_t const *classlist = _getObjc2ClassList(hi, &count);
    for (i = 0; i < count; i++) {
        Class cls = (Class)classlist[i];
        // Read class (emphasis)
        Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
        // Only if the new class parses the future class
        if(newCls ! = cls && newCls) {// When the class has been removed (empty memory), but not deleted.
            // Non-lazy loading implements future class resolutions
            resolvedFutureClasses = (Class *)realloc(resolvedFutureClasses, 
                        (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; }}}Copy the code
  • frommachoLoad the table information of the class
  • throughreadClassReading class
  • Future class resolution

Verify the interrupt points before and after readClass:

Conclusion:discover classesMain function callreadClassLoad class information. (Specific analysis belowreadClass)

remap classes

for (EACH_HEADER) {
    // Read macho's class reference table
    Class *classrefs = _getObjc2ClassRefs(hi, &count);
    for (i = 0; i < count; i++) {
        // Remap class references to prevent referenced classes from being reassigned
        remapClassRef(&classrefs[i]);
    }
    // Read macho's parent reference table
    classrefs = _getObjc2SuperRefs(hi, &count);
    for (i = 0; i < count; i++) {
        // Remap the parent class reference in case the referenced parent class is reassigned
        remapClassRef(&classrefs[i]); }}Copy the code
  • frommachoRead, respectively,classReference tables andThe parent classRefer to table
  • callremapClassRefRemap a reference to a class to prevent the referenced class from being reassigned
  • remapClassRef()remapClass()Remap class references.

Conclusion:remap classesThe main function is to remap class and parent class references to prevent redistribution

objc_msgSend_fixup

for (EACH_HEADER) {
    // select sel from macho
    message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
    if (count == 0) continue;
    for (i = 0; i < count; i++) {
        // Fix method references (mapping methods), such as alloc on top and objc_alloc on bottom
        // To prevent errors, LLVM is done at compile time. This is to prevent other effects from changing
        fixupMessageRef(refs+i); }}Copy the code
  • To obtainmachoIn theselMethod table
  • Remapping method reference (mapping method)
  • You wouldn’t normally go there, because the method maps tollvmThe phase has been dealt with.

Conclusion:objc_msgSend_fixupPrincipal action mappingselreference

discover protocols

for (EACH_HEADER) {
    // Create a protocol table (if the current class has a protocol)
    NXMapTable *protocol_map = protocols(a);bool isBundle = hi->isBundle(a);// Get protocol table from macho
    protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
    for (i = 0; i < count; i++) {
        // Read the protocol and re-map the protocol
        readProtocol(protolist[i], cls, protocol_map, isPreoptimized, isBundle); }}Copy the code
  • NXMapTableCreating a protocol table
  • To obtainmachoAgreement list
  • Remap the protocol and store it in the protocol table

Conclusion:discover protocolsMain function readagreement

readProtocol

static void
readProtocol(protocol_t *newproto, Class protocol_class,
             NXMapTable *protocol_map, 
             bool headerIsPreoptimized, bool headerIsBundle)
{
    // Insert the table method
    auto insertFn = headerIsBundle ? NXMapKeyCopyingInsert : NXMapInsert;
    // Get the old protocol
    protocol_t *oldproto = (protocol_t *)getProtocol(newproto->mangledName);
    if (oldproto) {
        // If an old protocol exists, it does not equal a new protocol
        if(oldproto ! = newproto) {if(headerIsPreoptimized && ! oldproto->isCanonical()) {
                // Get the cached protocol, that is, the old protocol
                auto cacheproto = (protocol_t *)
                    getSharedCachePreoptimizedProtocol(newproto->mangledName);
                if (cacheproto && cacheproto->isCanonical())
                    // Clear protocol (by and operation)
                    cacheproto->clearIsCanonical(a); }}}else if (headerIsPreoptimized) {
        // Get the preload protocol
        protocol_t *cacheproto = (protocol_t *)
            getPreoptimizedProtocol(newproto->mangledName);
        protocol_t *installedproto;
        if(cacheproto && cacheproto ! = newproto) { installedproto = cacheproto; }else {
            installedproto = newproto;
        }
        // If there is a preload protocol, insert; Otherwise, insert a new protocol
        insertFn(protocol_map, installedproto->mangledName, 
                 installedproto);
    } else {
        // Initialize the protocol class
        newproto->initIsa(protocol_class);  // fixme pinned
        // Insert a new protocol
        insertFn(protocol_map, newproto->mangledName, newproto); }}Copy the code
  • Determine whether there isThe old agreement, if existsremove.
  • If there is no and there isPreloaded mark, check the preloading cache protocol, if it exists, theninsert, otherwise,Insert a new.
  • If neither, thencreateNew protocol class, andinsertThe new agreement.

fix up @protocol references

for (EACH_HEADER) {
    // Get the macho protocol reference list
    protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
    for (i = 0; i < count; i++) {
        // Remap protocol references to prevent protocols from being reassigned
        remapProtocolRef(&protolist[i]); }}Copy the code
  • To obtainmachoIn theagreementRefer to table
  • Remap protocol references to prevent protocols from being reassigned. (Custom protocols don’t go this far)

Conclusion:fix up @protocol referencesThe main function is remappingagreementreference

discover categories

if (didInitialAttachCategories) {
    for (EACH_HEADER) {
        // Load the classification
        load_categories_nolock(hi); }}Copy the code
  • This is only done after the initial category, so it will not be done here.
  • Postponed toload_imagesThe call.

Conclusion:discover categoriesIt doesn’t loadclassification, the classification must be loaded inload_imagesafter

realize non-lazy classes

for (EACH_HEADER) {
    // Get a list of macho non-lazy loads
    classref_t const *classlist = hi->nlclslist(&count);
    for (i = 0; i < count; i++) {
        // Map non-lazy-loaded classes
        Class cls = remapClass(classlist[i]);
        if(! cls)continue;
        // Add a non-lazy-loaded class to the allocatedClasses table. This method adds the class's metaclass by default.
        addClassTableEntry(cls);
        // Initializes the non-lazy-loaded class
        realizeClassWithoutSwift(cls, nil); }}Copy the code
  • To obtainmachoA list of classes that are not lazily loaded
  • Load the class and add it toallocatedClassestable
  • Initialize a non-lazy-loaded class (realizeClassWithoutSwiftFunction subsequent analysis)
  • In general, the class implements+ loadMethod, will enter. because+loadThe class of the method appears in__objc_nlclslistIn the.

Non-lazy loading classes fall into three categories:

  • Self-actualize+ loadMethods (will appear in__objc_nlclslistC)
  • The subclass implements+ loadMethods (do not appear in__objc_nlclslistC)
  • Classification is implemented+ loadMethod (including its own classification and subclass classification, because the classification load is not inmap_imagesProcess, but inload_imagesProcess. So it’s not going to be there__objc_nlclslistC)

Conclusion:realize non-lazy classesInitializes classes that are not lazily loaded

realize future classes

// Implement the newly parsed 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");
        }
        // Initialize the future class
        realizeClassWithoutSwift(cls, nil);
        cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
    }
    // Free memory
    free(resolvedFutureClasses);
}
Copy the code

Realize Future classes initializes the future class. This logic is not normally executed.

ReadClass focuses on analysis

Load class and metaclass, mainly for the address association, the address associated with the operation of the class.

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName(a);// If there is no parent class (possibly weakly linked).
    // Normally, this is not done
    if (missingWeakSuperclass(cls)) {
        // Rebind the parent class to nil
        addRemappedClass(cls, nil);
        // Set the parent class to nil
        cls->setSuperclass(nil);
        return nil;
    }
    / / replace
    Class replacing = nil;
    if(mangledName ! =nullptr) {
        // Get the future class based on the class name
        // Normally, this is not done
        if (Class newCls = popFutureNamedClass(mangledName)) { ... }}if(headerIsPreoptimized && ! replacing) {CLS == getClass(name)
        ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
    } else {
        if (mangledName) {
            // Add gdb_objC_realized_classes to the aggregate table
            addNamedClass(cls, mangledName, replacing);
        } else {
            // Assert whether non-metaclasses exist
        }
        // add CLS to allocatedClasses table
        addClassTableEntry(cls);
    }
    
    // Normally, this is not done
    if (headerIsBundle) {
        // Set the flag bit
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA() - >data()->flags |= RO_FROM_BUNDLE;
    }
    return cls;
}
Copy the code

There’s a lot of judgment in this method, but through breakpoint analysis, normally you would do addNamedClass, addClassTableEntry.

addNamedClass

Add gDB_objC_realized_classes to the aggregate table

static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    Class old;
    // Search by name for classes that may not be implemented.
    if ((old = getClassExceptSomeSwift(name)) && old ! = replacing) {inform_duplicate(name, old, cls);
        Classes not looked up by name are stored in the non-metaclass table nonmeta_class_map
        addNonMetaClass(cls);
    } else {
        // If not, insert into the gdb_objC_realized_classes table
        NXMapInsert(gdb_objc_realized_classes, name, cls); }}Copy the code
  • The main purpose is toclsInserted into thegdb_objc_realized_classesIn the table (doneOnceTotal table created in
  • NXMapInsertAssociated classes and addresses are handled. While inserting the master table, proceedMapPair(key-value)The formal association of.

NXMapInsert

typedef struct _MapPair {
    const void *key;
    const void *value;
} MapPair;

void *NXMapInsert(NXMapTable *table, const void *key, const void *value) {
    / / to get buckets
    MapPair *pairs = (MapPair *)table->buckets;
    unsigned index = bucketOf(table, key);
    // Find the pair
    MapPair *pair = pairs + index;
    // New buckets address
    unsigned numBuckets = table->nbBucketsMinusOne + 1;
    
    if (isEqual(table, pair->key, key)) {
        const void *old = pair->value;
        // It is not necessary to avoid writing
        if(old ! = value) pair->value = value;return (void *)old;
    } else if (table->count == numBuckets) {
        // No space, rehash
        _NXMapRehash(table);
        return NXMapInsert(table, key, value);
    } else {
        unsigned index2 = index;
        while ((index2 = nextIndex(table, index2)) ! = index) { pair = pairs + index2;if (pair->key == NX_MAPNOTAKEY) {
                pair->key = key; pair->value = value;
                table->count++;
                // Determine the load factor
                if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
                return NULL;
            }
            if (isEqual(table, pair->key, key)) {
                const void *old = pair->value;
                // It is not necessary to avoid writing
                if(old ! = value) pair->value = value;return (void*)old; }}return NULL; }}Copy the code

The main function of this method is to transformclassTo join theTotal tableAnd do itAddress associated.

addClassTableEntry

Adding CLS to the allocatedClasses table also adds metaclases by default.

static void addClassTableEntry(Class cls, bool addMeta = true)
{
    // Get the allocatedClasses table
    auto &set = objc::allocatedClasses.get(a);// If the class does not exist, insert it directly
    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        // The metaclass is inserted by default, but the metaclass is not inserted
        addClassTableEntry(cls->ISA(), false);
}
Copy the code

The main function of this method is to transformclassandThe metaclassTo join theallocatedClassesIn the table (runtime_initCreated in theallocatedClassesTable).

summary

readClassThe core logic of theClass associated with an addressAnd joingdb_objc_realized_classeswithallocatedClassesIn the table

conclusion

To undertake the dyLD loading process, the class loading process is as follows:

supplement

Lazy loading and non-lazy loading

Lazy-loaded and non-lazy-loaded classes

  • Start classes initialized during the process in order to allocate on demandThe lessStarting speedThe faster the.
  • Implemented for non-lazy loading classes+loadMethod (subclass/class/self), the class will be preloaded, for+ loadCall to prepare.
  • For lazy-loaded classes, it is the first time a message is sentobjc_msgSend,lookUpImpOrForwardIt is initialized when a message is slowly searched.

The difference between lazy and non-lazy loading:

  • Lazy loading class: Data loading is delayed until the first message is sent.
    • lookUpImpOrForward
    • realizeClassMaybeSwiftMaybeRelock
    • realizeClassWithoutSwift
    • methodizeClass
  • Non-lazy-loaded classes:map_imagesLoad all class data.
    • readClass
    • _getObjc2NonlazyClassList
    • realizeClassWithoutSwift
    • methodizeClass