The last article about dyLD involved the _objc_init function in OBJC, but we didn’t dive into the process involved, so we’ll start with this one.

_objc_init

The definition of void _objc_init(void) can be found in objc/Source/objc-os.mm.

/*********************************************************************** * _objc_init * Bootstrap initialization. Registers our image notifier with dyld. * Called by libSystem BEFORE library initialization time * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /

void _objc_init(void)
{
    // initialized Initialized a local static variable. Ensure that it is initialized only once. The next time you call _objc_init, return directly
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init(a);// 1 one environment variable initialization
    
    tls_init(a);// 2 retail ️ local thread pool
    static_init(a);// 3 one system-level C++ constructor call
    runtime_init(a);// 4 retail ️ Runtime initialization
    exception_init(a);// 5 discount ️ Register listener on abnormal callbacks
    cache_init(a);// 6 ️ cache initialization
    _imp_implementationWithBlock_init(); // 7 one ️ initialize the Block flag of IMP

    // 8 discount registered callback notification, & is a parameter of reference type
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    // 9 ️ DYLD notification registration mark
    didCallDyldNotifyRegister = true;
#endif
}
Copy the code

Below we have a detailed look at the realization of 1 ️ to 9 discount ️.

environ_init

The environ_init method initializes environment variables. Before the project runs we can use the Edit Scheme… -> Run -> Arguments -> Environment Variables add Environment Variables and their corresponding values, they default to NO, we can add them according to our needs and set value to YES. The objc-env.h file lists all the environment variables, which are divided into three blocks starting with OBJC_PRINT_, OBJC_DEBUG_, and OBJC_DISABLE_ respectively. PRINT, DEBUG, DISABLE. Setting environment variables can help us deal with some problems more quickly. For example, add the OBJC_PRINT_LOAD_METHODS environment variable, and the console will print all the system classes in the project, our own class, and the +load method in the taxonomy.

/*********************************************************************** * environ_init * Read environment variables that affect the runtime. Read environment variables that affect runtime. * Also print environment variable help, if requested. We also print out helpful environment variables if needed. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void environ_init(void) 
{
    if (issetugid()) {
        // All environment variables are silently ignored when setuid or setgid.
        // When setuid or setgid is used, all environment variables are silently ignored.
        
        // This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves.
        // This also includes OBJC_HELP and OBJC_PRINT_OPTIONS.
        
        return; // ⬅️ return directly
    } 
    
    // Three local variables, false by default, then determine whether to set them to true in the first for loop below.
    bool PrintHelp = false;
    bool PrintOptions = false;
    bool maybeMallocDebugging = false;

    // Scan environ[] directly instead of calling getenv() a lot.
    // This optimizes the case where none are set.
    
    Environ []
    for (char**p = *_NSGetEnviron(); *p ! = nil; p++) {// If "Malloc", "DYLD", "NSZombiesEnabled" are detected, set maybeMallocDebugging to true
        if (0= =strncmp(*p, "Malloc".6) | |0= =strncmp(*p, "DYLD".4) | |0= =strncmp(*p, "NSZombiesEnabled".16))
        {
            maybeMallocDebugging = true;
        }
        
        // if it starts with "OBJC_" then skip it
        if (0! =strncmp(*p, "OBJC_".5)) continue;
        
        // Set PrintHelp to true if "OBJC_HELP=" is scanned
        if (0= =strncmp(*p, "OBJC_HELP=".10)) {
            PrintHelp = true;
            continue;
        }
        
        // If "OBJC_PRINT_OPTIONS=" is detected, set PrintOptions to true
        if (0= =strncmp(*p, "OBJC_PRINT_OPTIONS=".19)) {
            PrintOptions = true;
            continue;
        }
        
        The STRCHR function looks for the first match of a given character in a string.
        Char * STRCHR (const char * STR, int c),
        // Searches the string pointed to by STR for the first occurrence of the character c (an unsigned character).
        // The STRCHR function is included in the C library 
      
       .
      
        
        // find the position of the first = in p
        const char *value = strchr(*p, '=');
        if(! *value)continue;
        value++; // Then value increments (value is a char pointer, so value advances by one byte)
        
        The Settings element is a global immutable group of type option_t.
        // All option_t entries are listed in objc-env.h.
        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            // ⚠️
            // Get the environment variable from _NSGetEnviron,
            // Then iterate to see if you need to set the var member of the corresponding option_t instance stored in Settings to YES.
            const option_t *opt = &Settings[i];
            if ((size_t)(value - *p) == 1+opt->envlen  &&  
                0= =strncmp(*p, opt->env, opt->envlen))
            {
                *opt->var = (0= =strcmp(value, "YES"));
                break; }}}// Special case: enable some autorelease pool debugging
    // Special case: enable some automatic release pool debugging
    // when some malloc debugging is enabled and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO.
    // When some malloc debugging is enabled and OBJC_DEBUG_POOL_ALLOCATION is not set to a value other than NO.
    
    // Two ifs determine whether DebugPoolAllocation is set to true
    if (maybeMallocDebugging) {
        const char *insert = getenv("DYLD_INSERT_LIBRARIES");
        const char *zombie = getenv("NSZombiesEnabled");
        const char *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION");
        if ((getenv("MallocStackLogging") | |getenv("MallocStackLoggingNoCompact")
             || (zombie && (*zombie == 'Y' || *zombie == 'y'))
             || (insert && strstr(insert, "libgmalloc"&& ()))! pooldebug ||0= =strcmp(pooldebug, "YES")))
        {
            DebugPoolAllocation = true; }}// Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    // Prints OBJC_HELP and OBJC_PRINT_OPTIONS outputs.
    if (PrintHelp  ||  PrintOptions) {
        // The following two ifs output some prompts
        if (PrintHelp) {
            // Objective-C runtime debugging. Set variable=YES to enable.
            _objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
            // OBJC_HELP: describes available environment variables
            _objc_inform("OBJC_HELP: describe available environment variables");
            if (PrintOptions) {
                // OBJC_HELP has been set
                _objc_inform("OBJC_HELP is set");
            }
            // OBJC_PRINT_OPTIONS: Lists the options set
            _objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
        }
        if (PrintOptions) {
            // OBJC_PRINT_OPTIONS is set
            _objc_inform("OBJC_PRINT_OPTIONS is set");
        }
        
        // The following for loop prints the env of all environment variables in Settings and their descriptions.
        // Print the environment variable we set to YES to indicate that we set the environment variable.
        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];
            // Prints the existing environment variables with their corresponding descriptions
            if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
            
            // Prints the environment variable with the value of var set to YES, which tells us which environment variables are currently being set
            if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env); }}}Copy the code

Struct option_t and Settings array

Objc-env.h is a complete use of a large set of OPTION macros, defining a set of option_t instances, each representing an environment variable.

#include “objc-env.h” is used to replace #include “objc-env.h” with a large number of option_t instances in the objc-env.h file. The const option_t Settings[] array contains all option_t struct instances in objc-env.h.

Here we have selected some of the more important or familiar environment variables, such as: OBJC_DISABLE_TAGGED_POINTERS indicates whether to disable tagged Pointer optimization for NSNumber and NSString. OBJC_DISABLE_TAG_OBFUSCATION indicates whether to disable tagged OBJC_DISABLE_NONPOINTER_ISA Specifies whether to disable the NON-pointer ISA field.

struct option_t {
    bool* var;
    const char *env;
    const char *help;
    size_t envlen;
};

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
// OPTION(var, env, help)

OPTION( PrintImages,              OBJC_PRINT_IMAGES,               "log image and library names as they are loaded")
OPTION( PrintImageTimes,          OBJC_PRINT_IMAGE_TIMES,          "measure duration of image loading steps")...OPTION( DisableTaggedPointers,    OBJC_DISABLE_TAGGED_POINTERS,    "disable tagged pointer optimization of NSNumber et al.") 
OPTION( DisableTaggedPointerObfuscation, OBJC_DISABLE_TAG_OBFUSCATION,    "disable obfuscation of tagged pointers")
OPTION( DisableNonpointerIsa,     OBJC_DISABLE_NONPOINTER_ISA,     "disable non-pointer isa fields")...Copy the code

Set the OBJC_DISABLE_NONPOINTER_ISA

To demonstrate the use of OBJC_DISABLE_NONPOINTER_ISA, add OBJC_DISABLE_NONPOINTER_ISA to Environment Variables and set it to YES. Uintptr_t nonpointer we should remember how to determine whether the isa of an instance object isa non-pointer or a pointer 1, if the first bit of isa is 1 it is non-pointer otherwise it is pointer.

We create an instance object and print the contents of its ISA with OBJC_DISABLE_NONPOINTER_ISA set to YES/NS, respectively.

// ⬇️⬇️ OBJC_DISABLE_NONPOINTER_ISA is not set or is set to NO
(lldb) x/4gx person
0x108baa1a0: 0x011d8001000080e9 0x0000000000000000
0x108baa1b0: 0x6b636950534e5b2d 0x426863756f547265
(lldb) p/t 0x011d8001000080e9
// ⬇️ isa first is 1
(long) $1 = 0b0000000100011101100000000000000100000000000000001000000011101001

// ⬇️⬇️ OBJC_DISABLE_NONPOINTER_ISA is set to YES
(lldb) x/4gx person
0x108d04080: 0x00000001000080e8 0x0000000000000000
0x108d04090: 0x00000001a0080001 0x0000000100008028
(lldb) p/t 0x00000001000080e8
// ⬇️ isa is 0
(long) $1 = 0b0000000000000000000000000000000100000000000000001000000011101000
Copy the code

Set the OBJC_PRINT_LOAD_METHODS

To demonstrate the use of OBJC_PRINT_LOAD_METHODS, add OBJC_PRINT_LOAD_METHODS to the Environment Variables and set it to YES. Run the project and you can see all the load methods in the print project as follows.

objc[37659]: LOAD: category 'NSObject(NSObject)' scheduled for +load
objc[37659]: LOAD: +[NSObject(NSObject) load]

objc[37659]: LOAD: category 'NSObject(NSObject)' scheduled for +load
objc[37659]: LOAD: +[NSObject(NSObject) load]

objc[37659]: LOAD: class 'NSColor' scheduled for +load
objc[37659] : LOAD: class 'NSApplication' scheduled for +load
objc[37659] : LOAD: class 'NSBinder' scheduled for +load
objc[37659] : LOAD: class 'NSColorSpaceColor' scheduled for +load
objc[37659] : LOAD: class 'NSNextStepFrame' scheduled for +load
objc[37659] : LOAD: +[NSColor load]

objc[37659]: LOAD: +[NSApplication load]

objc[37659]: LOAD: +[NSBinder load]

objc[37659]: LOAD: +[NSColorSpaceColor load]

objc[37659]: LOAD: +[NSNextStepFrame load]

objc[37659]: LOAD: category 'NSError(FPAdditions)' scheduled for +load
objc[37659]: LOAD: +[NSError(FPAdditions) load]

objc[37659]: LOAD: class '_DKEventQuery' scheduled for +load
objc[37659] : LOAD: +[_DKEventQuery load]

objc[37659]: LOAD: class 'LGPerson' scheduled for +load
objc[37659] : LOAD: +[LGPerson load]
Copy the code

There are other environment variables in objc-env.h that are not shown here.

tls_init

_objc_pthread_DestroySpecific is the thread destruction function. The thread pair is stored in the thread’s local storage space with TLS_DIRECT_KEY as the Key. Thread Local Storage (TLS) : Thread Local Storage (TLS) : Thread Local Storage (TLS)

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

static_init

Run the C++ static constructor. Libc calls _objc_init() before DYld calls our static constructor, so we have to do it ourselves.

/***********************************************************************
* 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(a)
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) { inits[i](); }}Copy the code

Let’s examine the static_init function, where auto Inits = getLibobjcInitializers(&_MH_dylib_header, &count); The UnsignedInitializer function is obtained from the __objc_init_func area. After the UnsignedInitializer is retrieved, the for loop executes all the UnsignedInitializer functions.

GetLibobjcInitializers this function is defined using the GETSECT macro. (The name probably tells us what it does: fetch data from a region.)

GETSECT(getLibobjcInitializers, UnsignedInitializer, "__objc_init_func");

#define GETSECT(name, type, sectname)                                   \
    type *name(const headerType *mhdr, size_t *outCount) {              \
        return getDataSection<type>(mhdr, sectname, nil, outCount);     \
    }                                                                   \
    type *name(const header_info *hi, size_t *outCount) {               \
        return getDataSection<type>(hi->mhdr(), sectname, nil, outCount); The \}
Copy the code

GETSECT(getLibobjcInitializers, UnsignedInitializer, “__objc_init_func”) can be defined as two functions.

UnsignedInitializer *getLibobjcInitializers(const headerType *mhdr, size_t *outCount) {
    return getDataSection<UnsignedInitializer>(mhdr, "__objc_init_func", nil, outCount);
}

UnsignedInitializer *getLibobjcInitializers(const header_info *hi, size_t *outCount) {
    return getDataSection<UnsignedInitializer>(hi->mhdr(), "__objc_init_func", nil, outCount);
}
Copy the code

runtime_init

UnattachedCategories is a static variable defined in namespace objc: static unattachedCategories unattachedCategories; UnattachedCategories class UnattachedCategories: Public ExplicitInitDenseMap

It is used to classify classes, append classes to classes, clear Class data, and clear Class data.
,>

The static ExplicitInitDenseSet < Class > allocatedClasses; AllocatedClasses is a table of all classes (and metaclass) that have been allocated with objc_allocateClassPair. It is also a static variable declared in namespace objc.

Objc: : unattachedCategories init (32) are classified to initialize storage containers, objc: : allocatedClasses. The init () is to initialize the class storage containers. This method is used to initialize the containers that will be used later.

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init(a); }Copy the code

exception_init

Initialize libobJC’s exception handling system. Called by map_images.

Old_terminate is a static global pointer to the exception_init function. The set_terminate function is a function whose input arguments and return values are terminate_handler (function Pointers to void as arguments and return values). Assign the return value to old_terminate, taking the address of _objc_terminate as an argument.

static void (*old_terminate)(void) = nil;
Copy the code
typedef void (*terminate_handler)(a);
_LIBCPP_FUNC_VIS terminate_handler set_terminate(terminate_handler) _NOEXCEPT;
Copy the code
/*********************************************************************** * exception_init * Initialize libobjc's exception handling system. * Called by map_images(). * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
Copy the code

Exception_init is registered to listen for exception_callback. During the execution of the system method, exception_init triggers an interrupt, and an exception is reported. If we handle this method at the top level, we can catch the exception. Note: system method execution exception. We can listen for system exceptions from here, and let’s see what we can do. Let’s look at the _objc_Terminate method.

/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler. 
* 1. Check if there's an active exception
* 2. If so, check if it's an Objective-C exception
* 3. If so, call our registered callback with the object.
* 4. Finally, call the previous terminate handler.
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
    // OPTION( PrintExceptions, OBJC_PRINT_EXCEPTIONS, "log exception handling")
    // OBJC_PRINT_EXCEPTIONS is an environment variable defined in objc-env.h that determines whether to print logs based on its value
    if (PrintExceptions) {
        _objc_inform("EXCEPTIONS: terminating");
    }

    if (! __cxa_current_exception_type()) {
        // No current exception.
        (*old_terminate)();
    }
    else {
        // There is a current exception. Check if it's an objc exception.
        // There is currently an exception. Check if it is an objc Exception.
        @try {
            __cxa_rethrow();
        } @catch (id e) {
            // It's an objc object. Call Foundation's handler, if any.
            // It is an objc object. Invoke Foundation's handler (if any).
            (*uncaught_handler)((id)e);
            
            (*old_terminate)();
        } @catch(...). {// It's not an objc object. Continue to C++ terminate.
            // It is not an objc object. Perform C++ terminate.(*old_terminate)(); }}}Copy the code

@catch (id e) below we see that if an objC object is caught, uncaught_handler is executed. Uncaught_handler is a static global function.

// The input parameter is a pointer to void whose return value is id
typedef void (*objc_uncaught_exception_handler)(id _Null_unspecified /* _Nonnull */ exception);

/*********************************************************************** * _objc_default_uncaught_exception_handler * Default Uncaught Exception handler. Expected to be overridden by Foundation. It is expected to be reloaded by Foundation. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
static void _objc_default_uncaught_exception_handler(id exception)
{
}
static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;
Copy the code

Uncaught_handler is assigned to _objC_default_uncaught_EXCEPtion_handler by default, and its implementation is empty. If uncaught_handler is not assigned, It’s handled by the system.

Let’s see where uncaught_handler is assigned, so let’s search globally for uncaught_handler.

/*********************************************************************** * objc_setUncaughtExceptionHandler * Set a Handler for Uncaught Objective-c exceptions. * Sets the handler for uncaught Objective-c exceptions. * Returns the previous handler. * Returns the previous handler. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    // result Records the old value, used to return the value
    objc_uncaught_exception_handler result = uncaught_handler;
    
    // Set uncaught_handler to a new value
    uncaught_handler = fn;
    
    // Return the old value
    return result;
}
Copy the code

Objc_setUncaughtExceptionHandler method to catch exceptions Objective – C set a handler, and return before the handler. The method implementation also assigns the method passed in to uncaught_handler.

In _objc_Terminate we see that when an Objective-C exception is caught, our registered callback (uncaught_handler) is called using the exception object (NSException), Below we put the system to provide the default implementation is empty _objc_default_uncaught_exception_handler function, using objc_setUncaughtExceptionHandler replacement for our own function.

#import "HMUncaughtExceptionHandle.h"

@implementation HMUncaughtExceptionHandle

void TestExceptionHandlers(NSException *exception) {
    NSLog(@"🦷 🦷 🦷 % @ 🦷 🦷 🦷 % @", exception.name, exception.reason);
}

+ (void)installUncaughtSignalExceptionHandler {
    NSSetUncaughtExceptionHandler(&TestExceptionHandlers);
}

@end

#import <Foundation/Foundation.h>
#import "HMUncaughtExceptionHandle.h"

int main(int argc, const char * argv[]) {
    [HMUncaughtExceptionHandle installUncaughtSignalExceptionHandler];
    
    NSLog(@"🍀 🍀 🍀 % s", __func__);
    
    NSArray *tempArray = @[@(1), @ (2), @ (3)];
    NSLog(@"% @", tempArray[100]);
    
    return 0;
}
Copy the code

We put our custom TestExceptionHandlers NSSetUncaughtExceptionHandler functions assigned to uncaught_handler, when we are in the main function of active trigger a exception is an array, Our TestExceptionHandlers function is called. We saw the NSSetUncaughtExceptionHandler function in installUncaughtSignalExceptionHandler function, It is in NSException. H declared in (FOUNDATION_EXPORT void NSSetUncaughtExceptionHandler (NSUncaughtExceptionHandler * _Nullable);) , it is corresponding to the definition of objc_setUncaughtExceptionHandler function.

// ⬇️⬇️⬇️ calls our TestExceptionHandlers function when the exception occurs
2021- 06- 11 08:50:03.385083+0800 KCObjc[22433:2951328] 🦷 🦷 🦷 NSRangeException 🦷 🦷 🦷 * * * - [__NSArrayI objectAtIndexedSubscript:] : index100 beyond bounds [0.2]

2021- 06- 11 08:50:03.385154+0800 KCObjc[22433:2951328] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndexedSubscript:]: index 100 beyond bounds [0 .. 2]'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff20484083 __exceptionPreprocess + 242
    1   libobjc.A.dylib                     0x00007fff201bc17c objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff20538c82 _CFThrowFormattedException + 194
    3   CoreFoundation                      0x00007fff203f7991 +[NSNull null] + 0
    4   KCObjc                              0x0000000100003d34 main + 292
    5   libdyld.dylib                       0x00007fff2032d621 start + 1
    6?????0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndexedSubscript:]: index 100 beyond bounds [0 .. 2]'
terminating with uncaught exception of type NSException
Copy the code

So exception_init we’re right there, so let’s move on to the next function.

cache_init

Cache_init, as we can guess from its name, is related to cache initialization, and cache refers to method cache. The cache_init function is defined in the objc-cache.mm file that we looked at a million times before when we learned about method caching.

HAVE_TASK_RESTARTABLE_RANGES is a macro definition that has a value of 0 or 1 on different platforms.

// Define HAVE_TASK_RESTARTABLE_RANGES to enable usage of task_restartable_ranges_synchronize()
/ / enable task_restartable_ranges_synchronize

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

/ / ⬇ ️ ⬇ ️ ⬇ ️
#   define HAVE_TASK_RESTARTABLE_RANGES 1
/ / ⬆ ️ ⬆ ️ ⬆ ️

#endif
Copy the code

Objc_restartableRanges is a global task_restarTable_range_t array.

extern "C" task_restartable_range_t objc_restartableRanges[];

void cache_init(a)
{
#if HAVE_TASK_RESTARTABLE_RANGES
    // mach_MSG_type_number_t is currently an alias for an unsigned int
    mach_msg_type_number_t count = 0;
    // Kern_return_t is currently an alias of int
    kern_return_t kr;

    // Count the number of task_restarTable_range_t where location exists in objc_restartableRanges
    while (objc_restartableRanges[count].location) {
        count++;
    }
    
    // Register a set of restartable ranges for the current task.
    kr = task_restartable_ranges_register(mach_task_self(),
                                          objc_restartableRanges, count);
    if (kr == KERN_SUCCESS) return;
    
    // Stop running if registration fails
    _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)", kr, mach_error_string(kr));
    
#endif // HAVE_TASK_RESTARTABLE_RANGES
}
Copy the code

If we do a global search of objc_restartableRanges, we can see that it is traversed and read in the _collecting_in_critical function. The _collecting_in_critical function is used to determine whether the old method cache (the expanded old method cache table) can be collected and released, and returns TRUE if a thread is currently performing a cache read function. Cache garbage is not allowed to be collected while cache reading is in progress because it may still be using garbage memory. The old method cache table cannot be freed while another thread is reading the old method cache table.

_objc_restartableRanges is used by the Method dispatch Caching code to determine if any threads are actively scheduling in the cache.

_imp_implementationWithBlock_init

Initialize trampoline machinery. Normally this does nothing because everything is lazily initialized, but for some processes we eagerly load trampolines dylib.

Load libobjc-trampolines.dylib eagerly in some process. Some programs (most notably the QtWebEngineProcess used by older versions of embedded Chromium) enable a highly restrictive sandbox configuration file that blocks access to the dylib. If anything calls the imp_implementationWithBlock (as AppKit begins to do), we will crash when we try to load it. Loading the Sandbox configuration file here sets it up before enabling it and blocking it.

Trampolines is a static global variable of type TrampolinePointerWrapper.

static TrampolinePointerWrapper Trampolines;
Copy the code
/// Initialize the trampoline machinery. Normally this does nothing, as
/// everything is initialized lazily, but for certain processes we eagerly load
/// the trampolines dylib.
void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
    // Eagerly load libobjc-trampolines.dylib in certain processes. Some
    // programs (most notably QtWebEngineProcess used by older versions of
    // embedded Chromium) enable a highly restrictive sandbox profile which
    // blocks access to that dylib. If anything calls
    // imp_implementationWithBlock (as AppKit has started doing) then we'll
    // crash trying to load it. Loading it here sets it up before the sandbox
    // profile is enabled and blocks it.
    //
    // This fixes EA Origin (rdar://problem/50813789)
    // and Steam (rdar://problem/55286131)
    if (__progname &&
        (strcmp(__progname, "QtWebEngineProcess") = =0 ||
         strcmp(__progname, "Steam Helper") = =0)) {
        Trampolines.Initialize(a); }#endif
}
Copy the code

The _imp_implementationWithBlock_init method implementation and you can see that this is executed under OS, and this method is to initialize the Block flag of imp.

Let’s extend the imp_implementationWithBlock function. You can see its declaration in Runtime.h as follows:

/** * Creates a function that will call the block when the method is called Pointer to the function of. * * @param block The block that implements this method. Its signature should be: method_return_type ^(id self, method_args...) * The selector is not available as a parameter to this block. * The block is copied with \c Block_copy(). * * Argument block Implement the return value IMP block. Its signature should be: method_return_type ^(id self, method_args...) . (_cmd ignored) * selector cannot be used as an argument to this block. This block is copied using Block_copy(). * * @return The IMP that calls this block. Must be disposed of with \c imp_removeBlock. Imp_removeBlock must be used when a block and IMP are unassociated. * /
OBJC_EXPORT IMP _Nonnull
imp_implementationWithBlock(id _Nonnull block)
    OBJC_AVAILABLE(10.7.4.3.9.0.1.0.2.0);
Copy the code

It is defined in objc-block-trampolines.mm as follows:

IMP imp_implementationWithBlock(id block) 
{
    // Block object must be copied outside runtimeLock because it performs arbitrary work.
    block = Block_copy(block);

    // Trampolines must be initialized outside runtimeLock because it calls dlopen().
    Trampolines.Initialize(a);/ / lock
    mutex_locker_t lock(runtimeLock);

    return _imp_implementationWithBlockNoCopy(block);
}
Copy the code

The imp_implementationWithBlock function might get us confused, so let’s look at the use example in detail.


/ / 1 ⃣ ️ Answerer. H
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Answerer : NSObject

@end

NS_ASSUME_NONNULL_END

/ / 2 ⃣ ️ Answerer. M
#import "Answerer.h"

@implementation Answerer

@end

/ / 3 ⃣ ️ Answerer + DynamicallyProvidedMethod. H
#import "Answerer.h"

NS_ASSUME_NONNULL_BEGIN

@interface Answerer (DynamicallyProvidedMethod)

- (int)answerForThis:(int)a andThat:(int)b;
- (void)boogityBoo:(float)c;

@end

NS_ASSUME_NONNULL_END

/ / 4 ⃣ ️ Answerer + DynamicallyProvidedMethod. M
#import "Answerer+DynamicallyProvidedMethod.h"

@implementation Answerer (DynamicallyProvidedMethod)

@end

/ / 5 ⃣ ️ main
#import <Foundation/Foundation.h>
#import "Answerer.h"
#import "Answerer+DynamicallyProvidedMethod.h"
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    
    // Create a block. The first id of the block corresponds to the default self argument of our OC function
    int (^impyBlock)(id, int.int) = ^(id _self, int a, int b) {
        return a + b;
    };
    
    // Create an IMP from block
    int (*impyFunct)(id, SEL, int.int) = (void*)imp_implementationWithBlock(impyBlock);
    
    / / call block
    NSLog(@"🍀🍀🍀 impyBlock: %d + %d = %d".20.22.impyBlock(nil, 20.22));
    / / call the imp
    NSLog(@"🍀🍀🍀 impyFunct: %d + %d = %d".20.22.impyFunct(nil, NULL.20.22));
    
    // Get the instance of the class we are going to modify next
    Answerer *answerer = [[Answerer alloc] init];
    
    // Add impyFunct dynamically to Answerer, and then call it
    class_addMethod([Answerer class], @selector(answerForThis:andThat:), (IMP)impyFunct, "i@:ii");
    NSLog(@"🍀🍀🍀 Method: %d + %d = %d".20.20, [answerer answerForThis:20 andThat:20]);
    
    SEL _sel = @selector(boogityBoo:);
    float k = 5.0;
    IMP boo = imp_implementationWithBlock(^(id _self, float c) {
        NSLog(@"🍀🍀🍀 [%@ - %@%f] %f", [_self class], NSStringFromSelector(_sel), c, c * k);
    });
    class_addMethod([Answerer class], _sel, boo, "v@:f");
    
    [answerer boogityBoo:3.1415];
    
    return 0;
}

// 6 retail console print as follows:

2021- 06- 12 16:30:05.558548+0800 KCObjc[1543:50241] 🍀 🍀 🍀 impyBlock:20 + 22 = 42
2021- 06- 12 16:30:05.558636+0800 KCObjc[1543:50241] 🍀 🍀 🍀 impyFunct:20 + 22 = 42
2021- 06- 12 16:30:05.558695+0800 KCObjc[1543:50241] 🍀 🍀 🍀 Method:20 + 20 = 40
2021- 06- 12 16:30:05.558787+0800 KCObjc[1543:50241🍀🍀🍀 [answerer-boogityBoo:3.141500] 15.707500
Copy the code

See this article for more details: Implementation mechanism for imp_implementationWithBlock().

_dyld_objc_notify_register

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

The _DYLD_OBJC_notify_register parameter is &map_images, load_images, and unmap_image. In the previous dyLD article, we mentioned that _dyLD_OBJC_Notify_register is used to register some callback functions.

The _dyLD_OBJC_notify_register function is declared from dyLD_priv. h.

The _dyLD_OBJC_notify_register function is used only by the OBJC Runtime to register handlers to be called when mapped, unmapped, and initialized OBJC images. Dyld will call the mapped function with the images array containing the OBJC-image-info section. Those dylibs images will automatically increase the reference count, so ObjC will no longer need to call dlopen() on them to prevent them from being unloaded. During the call to _dyLD_OBJC_Notify_register (), dyLD calls the mapped functions with the loaded OBJC images. Dyld will also call the mapped functions in any subsequent dlopen() calls. When DyLD calls initializers in the image, dyLD calls init. This is when objC calls any +load methods in the image.

//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to call dlopen() on them to keep them from being unloaded.
// During the call to _dyld_objc_notify_register(), dyld will call the "mapped" function with already loaded objc images.
// During any later dlopen() call, dyld will also call the "mapped" function.
// Dyld will call the "init" function when dyld would be called initializers in that image.
// This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);
Copy the code

The _DYLD_OBJC_notify_register function is defined in the dyLD source code.

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
    dyld::registerObjCNotifiers(mapped, init, unmapped);
}
Copy the code

See internal _dyld_objc_notify_register function is called directly dyld: : registerObjCNotifiers function, And dyld: : registerObjCNotifiers mappd function called internal is initialize all images, and then call all the images after initialization of the init function. When objC is ready (at the end of the objC_init function), the _dyLD_OBJC_notify_register is called to tell DyLD it can load the class, and DyLD loads the class.

The three most important functions map_images, load_images, and unmap_image will be analyzed in the next article because this article is too long. Keep it up! 🎉 🎉 🎉

Refer to the link

Reference link :🔗

  • Principles and implementation of TLS(Thread Local Storage)
  • Imp_implementationWithBlock (
  • The underlying principle of iOS – dyLD and OBJC association
  • Dyld – 832.7.3
  • OC Basic principles -App startup process (DYLD loading process)
  • What is dyLD cache in iOS?
  • IOS advanced basic principles – application loading (dyLD loading process, class and classification loading)
  • What does the iOS application do before entering the main function?
  • Dyld load application startup details
  • Dynamic and static libraries in iOS
  • Link path problems in Xcode
  • IOS uses the Framework for dynamic updates
  • Namespace, and problem resolution for repeated definitions
  • C++ namespace namespace
  • Learn about Xcode’s process for generating “static libraries” and “dynamic libraries”
  • Hook static initializers