In the loading of iOS application, we learned that app was started by kernel boot, and then led by DYLD to complete the initialization of the running environment, load binary files into memory in accordance with the format with ImageLoader, dynamically link the dependent library, and load into objC defined structure by Runtime. After all initialization, DyLD calls the application’s main function.
Dyld and OBJC work together to initialize OBJC when DYLD loads the dynamic library. During the initialization, OBJC registers the callback function _DYLD_OBJC_Notify_register to notify DYLD to execute map_images. Load_images, unmap_images to complete mapping, loading, etc. (see iOS app loading for details).
This design is very necessary, a clear division of labor, each in its own place; Second, the Runtime takes over and makes the Runtime dynamic. Next we explore the loading process of iOS classes with the initialization of LibobJC.
0. Start with _objc_init
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init(a);// Read environment variables that affect the runtime
tls_init(a);// Handle thread key binding
static_init(a);// run the C++ static constructor
runtime_init(a);/ / the runtime runtime environment initialization, it mainly: unattachedCategories, allocatedClasses
exception_init(a);// Initialize libobJC's exception handling system
cache_init(a);// The cache condition is initialized
_imp_implementationWithBlock_init(); // initialize libobjc-trampolines.dylib
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
Copy the code
Before any image initialization, ensure that libSystem_initializer is executed properly. LibSystem_initializer is the highest priority and the system library must be initialized properly first. _objc_init is then called via libdispatch_init –> _os_object_init.
In _objc_init:
environ_init()
Read environment variables that affect the runtime.
tls_init()
Handles binding of thread keys, handles destructors for each thread’s data.
static_init()
Run the C ++ static constructor, liBC will call _objc_init() before DYLD calls our static constructor, so we have to do it ourselves.
runtime_init()
The Runtime runtime environment initializes the creation of two tables :unattachedCategories (the category table not yet attached to the class) and allocatedClasses (the table of all classes (and metaclases) assigned by objc_allocateClassPair).
exception_init()
Initialize libobJC’s exception handling system to monitor exceptions.
cache_init()
The cache condition is initialized
_imp_implementationWithBlock_init()
Initialize libobjc-trampolines.dylib. Usually this doesn’t do much because all initialization is lazy, but for some processes we can’t wait to load trampolines dylib.
_objc_init after the above init is done, inform dyLD to call map_images, load_images, and unmap_image when appropriate.
1. map_images
The job of map_images is to process a given image mapped by dyLD. Responsible for managing all symbols in files and dynamic libraries (classes, selectors, protocols, categories, etc.)
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
Copy the code
Call map_images_nolock in the map_images function.
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{...// Find all mirrors from objc's metadata.if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
for (auto func : loadImageFuncs) {
for (uint32_t i = 0; i < mhCount; i++) {
func(mhdrs[I]); }}}Copy the code
Find all image files from machO, call _read_images, perform all class registration and repair functions. Image loading is called after all Settings are complete. So the operations before loading the image are clustered in _read_images, which is what we’re focusing on. The source code is very long, simplified as follows:
1.1 _read_images
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
......
Create table: list of named classes that are not in dyLD shared cache, whether implemented or not.
if(! doneOnce) { doneOnce = YES;int namedClassesSize =
(isPreoptimized()? unoptimizedTotalClasses : totalClasses) *4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); }...// ② Fix @selector references (fix '@selector' confusion during precompilation)
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) continue;
bool isBundle = hi->isBundle(a); SEL *sels = _getObjc2SelectorRefs(hi, &count); UnfixedSelectors += count;for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
SEL sel = sel_registerNameNoLock(name, isBundle);
if(sels[i] ! = sel) { sels[i] = sel; }}}}// ③ Fix the unresolved Future class and mark the Bundle class
for (EACH_HEADER) {
classref_t const *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[I];
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
if(newCls ! = cls && newCls) { resolvedFutureClasses = (Class *)realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; }}}// fix the remapped class; (Some classes not loaded by the image file)
if (!noClassesRemapped()) {
for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[I]);
}
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[I]); }}}#if SUPPORT_FIXUP
⑤ Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
if (PrintVtables) {
_objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
"call sites in %s", count, hi->fname());
}
for (i = 0; i < count; i++) {
fixupMessageRef(refs+i); }}#endif
/ / 6 readProtocol
NXMapTable *protocol_map = protocols(a);protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (EACH_HEADER) {
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map, isPreoptimized, isBundle); }}// ⑦ Fix up @protocol references
for (EACH_HEADER) {
if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
continue;
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[I]); }}/ / end load_categories_nolock
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi); }}// ⑨ non-lazy class loading
for (EACH_HEADER) {
classref_t const *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if(! cls)continue;
addClassTableEntry(cls);
realizeClassWithoutSwift(cls, nil); }}// ⑩ Realize future classes
// Realize newly resolved 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");
}
realizeClassWithoutSwift(cls, nil);
cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
}
free(resolvedFutureClasses);
}
if (DebugNonFragileIvars) {
realizeAllClasses();
}
}
Copy the code
The simplified summary is as follows:
- ① : Condition control is only done once:
Create a table gdb_objC_realized_classes to store a list of named classes that are not in the dyLD shared cache, whether implemented or not
- ② : Fix @selector references
Fix a messy problem with @selector during precompilation, so we know,SELIs a string with an address. Although the names of the two methods are the same, the addresses of the methods are not necessarily the same.As above, it is possible to have multiple frameworks
The init method
In the system, the address of the method is read according to the offset of the frame in the main procedure and the offset of the method in the frame. So, we need to tweak at sign Selector.
- ③ : Fix unresolved Future classes.
Load the list of classes from the MachO __objc_classList section, traverse the list of classes, do readClass, if the result of the readClass is different from the class in the list, do repair, but this usually does not occur, only the class has been moved and not deleted. In readClass, take the name of the class from CLS ->mangledName(), associate the name with the address, and insert it into the gDB_objC_realized_classes created in step 1. At the same time, insert the class and metaclass into the allocatedClasses table. In this step, readClass loads the address and name of the class into memory.
- Fix remapping class:
If there are classes that have not been loaded by the image file, remap at this point.
- ⑤ : Repair some old ones
objc_msgSend_fixup
callSome old message fixes are enforced, such as alloc -> objC_alloc, allocWithZone -> objc_allocWithZone, etc.
- 6: readProtocol:
Create a hash table storing proctol, read the protocol list from the [MachO] __objc_protolist section, iterate over the protocol list, perform readProtocol, add the protocol to the proctol table, register in memory.
- ⑦ : Fix up @protocol References
The pre-optimized image may already have the correct protocol reference, but we can’t be sure, so a fix is done here to prevent the referenced protocol from being reassigned.
- End: load_categories
Load classification, it is important to note that this does not load classification, only after didInitialAttachCategories assignment to true, And didInitialAttachCategories assignment to true process is in _dyld_objc_notify_register calls after the completion of the first load_images call assignment.
- ⑨ : loading non-lazy classes
What is done here is the implementation of a non-lazy-loaded class, that is, a class that implements the +load method. AddClassTableEntry (CLS) reads a list of non-lazy classes from __objc_nlclsListSection (MachO). Insert non-lazy-loaded classes into the class table and load them into memory. In step 3, we load the class into memory with an address and name, and finally execute realizeClassWithoutSwift to complete the structure of the class.
- ⑩:realize future classes
If there are future classes being processed, they need to be implemented here in case CF manipulates them. The implementation here is also through realizeClassWithoutSwift.
_read_images does much of this, but it can be simplified if you focus only on the loading process of the class:
- 1. The table
gdb_objc_realized_classes
Creating a table of named classes that are not in the shared cache does not matter whether the class is implemented or not.
- 2.
readClass
:
Insert gDB_objC_realized_CLASSES and allocatedClasses into MachO __objc_classList. From there, the class is loaded into memory and has an address and a name.
- 3.
realizeClassWithoutSwift
Implement the details of the class (RO, RW, etc.) to ensure the structural integrity of the class.
Specific readClass and realizeClassWithoutSwift code analysis is as follows:
1.2 readClass
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->mangledName(a);// If there is a parent missing or weak-linked in the inheritance chain, ignore the class and return nil
if (missingWeakSuperclass(cls)) {
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING class '%s' with "
"missing weak-linked superclass",
cls->nameForLogging());
}
addRemappedClass(cls, nil);
cls->superclass = nil;
return nil;
}
cls->fixupBackwardDeployingStableSwift(a); Class replacing = nil;// If the class is a previously allocated class for future processing, copy the objc_class data to the Future class, saving the FUTURE's RW data block
if (Class newCls = popFutureNamedClass(mangledName)) {
if (newCls->isAnySwift()) {
_objc_fatal("Can't complete future class request for '%s' "
"because the real class is too big.",
cls->nameForLogging());
}
class_rw_t *rw = newCls->data(a);const class_ro_t *old_ro = rw->ro(a);memcpy(newCls, cls, sizeof(objc_class));
rw->set_ro((class_ro_t *)newCls->data());
newCls->setData(rw);
freeIfMutable((char *)old_ro->name);
free((void *)old_ro);
addRemappedClass(cls, newCls);
replacing = cls;
cls = newCls;
}
// ASSERT, if it is a pre-optimized class and not a Future class
if(headerIsPreoptimized && ! replacing) {ASSERT(getClassExceptSomeSwift(mangledName));
} else {
Add the address and name to the gdb_objc_realized_classes table
addNamedClass(cls, mangledName, replacing);
// Add the allocatedClasses table
addClassTableEntry(cls);
}
// for future reference: shared cache never contains MH_BUNDLEs
if (headerIsBundle) {
cls->data()->flags |= RO_FROM_BUNDLE;
cls->ISA() - >data()->flags |= RO_FROM_BUNDLE;
}
return cls;
}
Copy the code
ReadClass processing:
- From 1.
cls->mangledName()
Gets the name of the class - 2. If the parent class is missing or weak-linked in the inheritance chain, ignore the class directly (the class is incomplete) and return nil;
- 3. If the class is a previously allocated class for future processing, copy the objC_class data to the Future class, saving the future’s data
rw
The data block; - 4. ASSERT if it is a pre-optimized class and not a Future class. Otherwise,
Execute addNamedClass to associate the address and name of the class and insert the gDB_objC_realized_classes table. Insert the allocatedClasses table with addClassTableEntry;
1.2.1 addNamedClass
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
runtimeLock.assertLocked(a); Class old;if ((old = getClassExceptSomeSwift(name)) && old ! = replacing) {inform_duplicate(name, old, cls);
// getMaybeUnrealizedNonMetaClass uses name lookups.
// Classes not found by name lookup must be in the
// secondary meta->nonmeta table.
addNonMetaClass(cls);
} else {
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
ASSERT(! (cls->data()->flags & RO_META));
}
Copy the code
Determine if a class with that name already exists in the gDB_objC_realized_classes table. If so, insert nonMetaClasses. Insert the GDB_objC_realized_CLASSES table if it does not exist.
1.2.2 addClassTableEntry
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
runtimeLock.assertLocked(a);auto &set = objc::allocatedClasses.get(a);ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
Copy the code
If the class is known at run time (such as in a shared cache, in a data segment with a loaded image, or already allocated with obj_allocateClassPair), then you do not need to insert the table; otherwise, you insert the allocatedClasses table, along with the class’s metaclass. The allocatedClasses table is created at runtime_init().
At this point, the class in MachO is loaded into memory and has a name and address, but the data is not associated, and the data association is carried out in realizeClassWithoutSwift.
1.3 realizeClassWithoutSwift
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked(a);class_rw_t *rw;
Class supercls;
Class metacls;
if(! cls)return nil;
if (cls->isRealized()) return cls; // If the class is implemented, it returns directly
ASSERT(cls == remapClass(cls));
auto ro = (const class_ro_t *)cls->data(a);// Fetch class information from data ()
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data(a); ro = cls->data() - >ro(a);ASSERT(! isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc<class_rw_t> (); rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
#if FAST_CACHE_META
if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
cls->chooseClassArrayIndex(a);if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex(),
cls->isSwiftStable()?"(swift)" : "",
cls->isSwiftLegacy()?"(pre-stable swift)" : "");
}
// Implement superclasses and metaclasses, if they are not already implemented.
// For the root class, this is done after setting rw_realize above.
For the root metaclass, this needs to be done after selecting the class index.
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA. Some handling of NONPOINTER_ISA#endif
// Update superclass and metaclass in case of remapping
cls->superclass = supercls;
cls->initClassIsa(metacls);
if(supercls && ! isMeta)reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
cls->setInstanceSize(ro->instanceSize);
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor(a);if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// Propagate the associated object disallow flag from ro or superclass.
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// Connect this class to its superclass's subclass lists
// Connect this class to the subclass list of its superclass
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
// Execute methodizeClass, paste method list, etc.
methodizeClass(cls, previously);
return cls;
}
Copy the code
RealizeClassWithoutSwift is simplified to understand as follows:
-
CLS ->data() reads class information from CLS ->data(), if future class, assign values to rw and ro; If it is a normal class, open up the RW space, assign a value to ro, and copy it into the RW.
-
Recursively implements the parent and metaclass of the class, and then updates the parent and metaclass of the class for remapping. Ensure the integrity of the inheritance chain and the ISA chain.
-
Assign some flags from ro to RW.
-
If the parent class exists, link the class to the subclass list of its parent class, otherwise as a new root class.
-
Perform methodizeClass
1.3.1 methodizeClass
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked(a);bool isMeta = cls->isMetaClass(a);auto rw = cls->data(a);auto ro = rw->ro(a);auto rwe = rw->ext(a);// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods(a);if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
if (rwe) rwe->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
#if DEBUG
// Debug: sanity-check all SELs; log method list contents
for (const auto& meth : rw->methods()) {
if (PrintConnecting) {
_objc_inform("METHOD %c[%s %s]", isMeta ? '+' : The '-',
cls->nameForLogging(), sel_getName(meth.name));
}
ASSERT(sel_registerName(sel_getName(meth.name)) == meth.name);
}
#endif
}
Copy the code
MethodizeClass method, mainly does these things:
- ① Install class method list
(method_list_t)
;- ② Install class property list
(property_list_t)
;- ③ Protocol list of install class
(protocol_list_t)
;- (4) the Attach categories.
- Install class list (method_list_t)
method_list_t *list = ro->baseMethods(a);if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
if (rwe) rwe->methods.attachLists(&list, 1);
}
Copy the code
BaseMethods () is read from ro and, if the list has a value, the method list is preprocessed through prepareMethodLists
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle)
{
runtimeLock.assertLocked(a);if (addedCount == 0) return;
if (baseMethods) {
ASSERT(cls->hasCustomAWZ() && cls->hasCustomRR() && cls->hasCustomCore());
}
for (int i = 0; i < addedCount; i++) {
method_list_t *mlist = addedLists[I];
ASSERT(mlist);
// Fixup selectors if necessary
if(! mlist->isFixedUp()) {
fixupMethodList(mlist, methodsFromBundle, true/*sort*/); }}if (cls->isInitialized()) {
objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount);
objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount);
objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount); }}Copy the code
There are special cases of RR/AWZ/Core for some classes of methods, but we don’t need to do anything because the default RR/AWZ/Core is never set before the setInitialized() method executes.
The method list is then sorted using fixupMethodList, which is the premise for binary lookup of the method list in the method lookup process: ensure that the methods in the method list are ordered.
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
runtimeLock.assertLocked(a);ASSERT(! mlist->isFixedUp());
if(! mlist->isUniqued()) {
mutex_locker_t lock(selLock);
// Unique selectors in list.
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name);
meth.name = sel_registerNameNoLock(name, bundleCopy); }}// Sort by selector address.
if (sort) {
method_t::SortBySELAddress sorter;
std::stable_sort(mlist->begin(), mlist->end(), sorter);
}
// Mark method list as uniqued and sorted
mlist->setFixedUp(a); }Copy the code
Fixup does two things: Unique and Sort.
Unique: to ensure the uniqueness of the method; Sort: Sort the list of methods by their name;
struct SortBySELAddress : public std::binary_function<const method_t&, const method_t&, bool> { bool operator() (const method_t& lhs, const method_t& rhs) { return lhs.name < rhs.name; }};Copy the code
Finally, the list of marked methods is in the fixed state and no further fixup is required.
After the work of prepareMethodLists is completed, judge whether RWE exists. If so, rWE ->methods.attachLists. At this time, RWE does not exist, here is a supplementary note:
Tips:
In the analysis of the structure of iOS classes, we know that some data information of a class is stored in RO at compile time, including the name, method, protocol, instance variables and other information determined at compile time. When a class is loaded by Runtime, the Runtime assigns it an additional RW for reading/writing.
Ro is read-only and stores information about fields determined at compile time. The RW is created at Runtime. It first copies the contents of the RO and then adds information about classes, attributes, methods, protocols, and so on.
For classes that have dynamic change behavior, this dynamic content is extracted into the RWE.
So far, the class loading process, for ro, rW has been assigned, and the rWE assignment is triggered when the class changes dynamically. At this time, RWE does not exist, so rWE ->methods.attachLists is not required. So when is rwe assigned?
class_rw_ext_t *extAllocIfNeeded(a) {
auto v = get_ro_or_rwe(a);if (fastpath(v.is<class_rw_ext_t* > ())) {return v.get<class_rw_ext_t* > (); }else {
return extAlloc(v.get<const class_ro_t* > ()); }}Copy the code
rwe
The creation is throughextAllocIfNeeded
. searchextAllocIfNeeded
The following result is obtained.
Corresponding to: AttachCategories, demangledName, class_setVersion, addMethod, addMethods, class_addProtocol, _class_addProperty, Objc_duplicateClass. All are dynamically modified behaviors.
If rWE has been created and method list has value, attachLists(&list, 1)
- Install class property_list_t
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
Copy the code
If the RWE has been created and the property list has a value, attachLists(& Proplist, 1)
- ③ Install class protocol_list_t
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
Copy the code
If rWE has been created and the protocol list has a value, attachLists(& Protolist, 1)
See attachLists function is executed as long as the conditions are met
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t: :byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if(! list && addedCount ==1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t: :byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0])); }}Copy the code
AttachLists function distinguishes three cases, and we analyze them according to the execution process as follows:
- 0 lists –> 1 list
In the process of starting from nothing, there is nothing in lists yet, so just assign the value of new List.
- 1 list -> many lists
Now lists already have the array assigned in [first]. Add the size to the original size to calculate the required capacity, according to the size of the array() space and assign: Put the oldList at the end of the array, and the new (addedLists), starting with the first address of lists, in the size of addedCount, into lists. In this way, the old data is put last and the new data is put first.
- 【第 二 句 】 Many lists -> many lists
In [second], array() is already assigned, so when a new array is attached, the array() space is reopened, still the sum of the old array and the new data capacity. Move the old data to the end and copy the new data to the front of the old data.
- (4) the Attach categories
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
Copy the code
Attach Categories is the process of adding categories. This part is discussed in the details of iOS categories.
At this point, the loading of the class (except for classification) is almost complete, and the structure of the class is almost complete through these steps. ReadClass associates the address of the class with the name and loads it into memory. Through realizeClassWithoutSwift to class ro, RW complete assignment; MethodizeClass is used to complete the methods, attributes and protocols of the class, and complete the loading of the classification. The dynamic modification portion of the class is also stored in RWE when there is behavior such as dynamic modification.
This is all done in map_images. Let’s move on to load_images.
2. load_images
When the image file is mapped, load_images is then executed to handle the +load method for the image that has been mapped in dyLD.
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if(! didInitialAttachCategories && didCallDyldNotifyRegister) { didInitialAttachCategories =true;
loadAllCategories(a); }if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods(a); }Copy the code
First, if the image file of libobjc.a.dylib is mapped and load_images is executed for the first time, loadAllCategories() is called to complete the loading of all categories. (Classification related content is not discussed in this chapter, put in the iOS classification details unified discussion);
There are many dynamic libraries that the system depends on. Each dynamic library checks whether the __objc_nlclslist and __objc_nlcatList in the current dynamic library (MachO) are full when executing load_images. There is no direct return. If so, execute prepare_load_methods
2.1 prepare_load_methods
void prepare_load_methods(const headerType *mhdr)
{
size_t count, I;
runtimeLock.assertLocked(a);/ / class load
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// Load by category
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[I];
Class cls = remapClass(cat->cls);
if(! cls)continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA() - >isRealized());
add_category_to_loadable_list(cat); }}Copy the code
Prepare_load_methods does two things:
- Read from the MachO header of the current dynamic library
__objc_nlclslist
, the implementation ofschedule_class_load
;
- Read from the MachO header of the current dynamic library
__objc_nlcatlist
, then forces the main class to implement, and then executeadd_category_to_loadable_list
, will be executed+load
Category added toloadable_categories
In the table.
2.1.1 schedule_class_load
static void schedule_class_load(Class cls)
{
if(! cls)return;
ASSERT(cls->isRealized()); // _read_images should realize
// If the add_class_to_loadable_list command has been executed, you do not need to add it
if (cls->data()->flags & RW_LOADED) return;
// Make sure the parent class executes first
schedule_class_load(cls->superclass);
/ / table
add_class_to_loadable_list(cls);
CLS has executed add_class_to_loadable_list
cls->setInfo(RW_LOADED);
}
Copy the code
Blocks classes that do not exist or are not implemented, and filters classes that have executed add_class_to_loadable_list. When the conditions are met to proceed down, ensure that the class’s parent performs +load before the class (if the parent implements + LOAD), then perform add_class_to_loadable_LIST insertion, and mark CLS as completed insertion.
2.1.1.1 add_class_to_loadable_list
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked(a);// get the IMP of the class +load method to store in the loadable_classes table
method = cls->getLoadMethod(a);if(! method)return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
// All the +load methods in each dynamic library are stored in the same table
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
Copy the code
Ro ()->baseMethods() in CLS by getLoadMethod(), iterate query method, find the IMP return method, perform insert table operation, do not find direct return.
The classes that need to perform +load in each dynamic library are stored in the same table loadable_classes, and the occupied space is compared with the created space to determine whether the space needs to be expanded and re-created.
2.1.2 add_category_to_loadable_list
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked(a); method = _category_getLoadMethod(cat);// Don't bother if cat has no +load method
if(! method)return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
_category_getClassName(cat), _category_getName(cat));
}
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
Copy the code
Category insert table loadable_categories insert table loadable_categories into cat->classMethods by _category_getLoadMethod.
The important thing to note here is that before executing the add_category_to_loadable_list operation, the implementation of the main class realizeClassWithoutSwift must be completed.
2.2 call_load_methods ()
Prepare_load_methods () calls call_load_methods() to load the classes, parent classes, and classes.
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked(a);// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush(a);do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads(a); }// 2. Call category +loads ONCE
more_categories = call_category_loads(a);// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
Copy the code
Enter the do… While (” loadable_classes “, “loadable_categories”, “loadable_categories”, “loadable_categories”, “loadable_categories”, “loadable_categories”, “loadable_categories”, “loadable_categories”, “loadable_categories”); Execute the +load method.
Thus, the +load method can be called.
3. Summary
This article mainly starts with _objc_init, combined with map_images and load_images to explore the class loading process. If you’re not familiar with Dyld, I suggest you take a look at my other post about iOS app loading, which will be a little clearer.
Although the loading details of a class are the same, the loading timing is different, so it’s worth adding a few words here about lazy-loaded and non-lazy-loaded classes.
Added: lazy-loaded and non-lazy-loaded classes
Non-lazy-loaded classes: classes that are loaded at the map_iamges stage as the program starts; Lazy-loaded class: A class that is loaded when it is first used (usually the first time a message is sent).
We can implement the +load method to force a class to load early, that is, not lazily.
The stack looks like this for several implementations of the +load method:
- The main class implementation
+load
When the main class implements +load, the class is implemented in step 9 of _read_images.
- The subclass implementation
+load
When a subclass implements +load, the stack above does realizeClassWithoutSwift twice, and if you’ve been paying close attention, you’ll remember that in realizeClassWithoutSwift, in order to preserve the integrity of the inheritance chain, It’s going to recursively call realizeClassWithoutSwift to implement the parent class.
- Classification implementation
+load
RealizeClassWithoutSwift will be called when prepare_load_methods is implemented to ensure that this class is implemented first. So the stack information looks like this.
- Failed to realize
+load
When none of the above +load is implemented, the class implementation will wait until it is used for the first time, which is generally the case when it is time to send a message. In the slow lookup process of a message, it is logical to implement the class here if it is not already implemented in order to keep the program running.
If the article is misleading, please correct it, if you feel helpful, you can also give me a praise 😁 thank you!