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 allocatedClassesexception_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 support
THREAD_KEYS
, if supported, according toTLS_DIRECT_KEY
Read the threadkey
.- If not, create a thread directly
key
.
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 static
C++
Call to the constructor of. indyld
calldoModInitFunctions
Before the static constructor,objc
Call your own constructor. Because at this timeobjc
Initialization of, yeahsection
I’m making a substitution, so I’m going todyld
It’s not going to call this one.- Internally there are two ways (from
macho
File 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_func
Was modified to be called__objc_init_func
.SECTION_TYPE
forS_MOD_INIT_FUNC_POINTERS (0 x9)
__init_offsets
Was modified to be called__objc_init_offs
.SECTION_TYPE
forS_INIT_FUNC_OFFSETS (0 x16)
__mod_term_func
Was modified to be called__objc_term_func
.SECTION_TYPE
forS_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 capture
Oc object
The exception.objc_setUncaughtExceptionHandler
Corresponding upper layer methodNSSetUncaughtExceptionHandler
.- The custom of
Monitoring method
As far as possible ondidFinishLaunchingWithOptions
To prevent otherSDK
Coverage. Too muchSDK
callNSSetUncaughtExceptionHandler
Sometimes there is confusion, and the performance is capturedcrash
There’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 executionload
methodsunmap_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_images
Function source code for code block collapsed after each foundlog
Illustrate the role of the code fast. The main steps are as follows:
- Condition control for a load. (
doneOnce
)- Fixed precompile phase
@selector
The 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:
readProtocol
Loading 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
taggedPointer
confusion- By load factor
load factor
Calculate the total table size- Create a total table containing
runtime_init
To create theunattachedCategories
andallocatedClasses
table- Total table
gdb_objc_realized_classes
, whether the class is instantiated or not.
Conclusion:
doneOnce
Create 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
sel
Is in thedyld
The load,sels[i]
frommacho
Read out, frommacho
The read address may be jumbled.sels[i] = sel
Is to overwrite the address, because indyld
Loaded outsel
The address is valid.- To find the
sel
Call path to:sel_registerNameNoLock
→__sel_registerName
→search_builtins
→_dyld_get_objc_selector
. clearlysel
by_dyld_get_objc_selector
To obtain.
Conclusion:
unfixedSelectors
Main action repairsel
Address 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
- from
macho
Load the table information of the class- through
readClass
Reading class- Future class resolution
Verify the interrupt points before and after readClass:
Conclusion:
discover classes
Main function callreadClass
Load 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
- from
macho
Read, respectively,class
Reference tables andThe parent class
Refer to table- call
remapClassRef
Remap a reference to a class to prevent the referenced class from being reassignedremapClassRef()
→remapClass()
Remap class references.
Conclusion:
remap classes
The 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 obtain
macho
In thesel
Method table- Remapping method reference (mapping method)
- You wouldn’t normally go there, because the method maps to
llvm
The phase has been dealt with.
Conclusion:
objc_msgSend_fixup
Principal action mappingsel
reference
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
NXMapTable
Creating a protocol table- To obtain
macho
Agreement list- Remap the protocol and store it in the protocol table
Conclusion:
discover protocols
Main 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 is
The old agreement
, if existsremove
.- If there is no and there is
Preloaded mark
, check the preloading cache protocol, if it exists, theninsert
, otherwise,Insert a new
.- If neither, then
create
New protocol class, andinsert
The 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 obtain
macho
In theagreement
Refer to table- Remap protocol references to prevent protocols from being reassigned. (Custom protocols don’t go this far)
Conclusion:
fix up @protocol references
The main function is remappingagreement
reference
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 to
load_images
The call.
Conclusion:
discover categories
It doesn’t loadclassification
, the classification must be loaded inload_images
after
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 obtain
macho
A list of classes that are not lazily loaded- Load the class and add it to
allocatedClasses
table- Initialize a non-lazy-loaded class (
realizeClassWithoutSwift
Function subsequent analysis)- In general, the class implements
+ load
Method, will enter. because+load
The class of the method appears in__objc_nlclslist
In the.
Non-lazy loading classes fall into three categories:
- Self-actualize
+ load
Methods (will appear in__objc_nlclslist
C)- The subclass implements
+ load
Methods (do not appear in__objc_nlclslist
C)- Classification is implemented
+ load
Method (including its own classification and subclass classification, because the classification load is not inmap_images
Process, but inload_images
Process. So it’s not going to be there__objc_nlclslist
C)
Conclusion:
realize non-lazy classes
Initializes 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 to
cls
Inserted into thegdb_objc_realized_classes
In the table (doneOnce
Total table created inNXMapInsert
Associated 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 transform
class
To join theTotal table
And 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 transform
class
andThe metaclass
To join theallocatedClasses
In the table (runtime_init
Created in theallocatedClasses
Table).
summary
readClass
The core logic of theClass associated with an address
And joingdb_objc_realized_classes
withallocatedClasses
In 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 demand
The less
Starting speedThe faster the
.- Implemented for non-lazy loading classes
+load
Method (subclass/class/self), the class will be preloaded, for+ load
Call to prepare.- For lazy-loaded classes, it is the first time a message is sent
objc_msgSend
,lookUpImpOrForward
It 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_images
Load all class data.
readClass
_getObjc2NonlazyClassList
realizeClassWithoutSwift
methodizeClass