preface
In the last chapter, we learned about the loading process of the application program. IOS used dyLD to load the corresponding file into the memory, and then called the main function to start the App. We mainly studied the process during the period before main function. And when were the rW and RO written?
Source code analysis
To analyze the loading of a class, we still start with the source code, according to the execution of the source code to slowly drill down, analyze what is done at each stage. Start with the _objc_init method that App starts
_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();
tls_init();
static_init();
runtime_init();
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
Copy the code
The _objc_init function does the following:
- Environment variable initialization
- Binding of thread keys
- Run the C++ static constructor
- Runtime Initializes the Runtime environment
- Initialize libobJC’s exception handling system
- The cache condition is initialized
- Start the callback mechanism
- Image file loading
Let’s look at the _dyLD_OBJC_Notify_register method and see how it loads the class.
_dyld_objc_notify_register
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
Copy the code
The above method contains calls to three methods, map_images, load_images, and unmap_image. Let’s look at map_images first.
map_images
/*********************************************************************** * map_images * Process the given images which are being mapped in by dyld. * Calls ABI-agnostic code after taking ABI-specific locks. * * Locking: write-locks runtimeLock **********************************************************************/ void map_images(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[]) { mutex_locker_t lock(runtimeLock); return map_images_nolock(count, paths, mhdrs); }Copy the code
Map_images calls the map_images_NOLock method. Look at the definition of map_images_NOLock
void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]) { ...... If (hCount > 0) {read_images(hList, hCount, totalClasses, unoptimizedTotalClasses); }... // omit some code}Copy the code
The _read_images function is called from map_images_NOLock to read the image file. Next, look at the definition of the _read_images function.
_read_images
Through the reading _read_images method, the following 10 processes are mainly involved:
- Condition control for a load
- Fix the 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
- Read protocols when there are protocols in the class
- Fix protocols that were not loaded
- Classification process
- Class loading processing
- For classes that are not processed, optimize those that are violated
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { ...... // omit some code if (! doneOnce) { ...... Int namedClassesSize = (isPreoptimized()? Int namedClassesSize = (isPreoptimized()? unoptimizedTotalClasses : totalClasses) * 4 / 3; Gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); ts.log("IMAGE TIMES: first time tasks"); } // Discover classes. Fix up unresolved future classes. Mark bundle classes. bool hasDyldRoots = dyld_shared_cache_some_image_overridden(); for (EACH_HEADER) { ...... classref_t const *classlist = _getObjc2ClassList(hi, &count); bool headerIsBundle = hi->isBundle(); bool headerIsPreoptimized = hi->hasPreoptimizedClasses(); for (i = 0; i < count; i++) { Class cls = (Class)classlist[i]; Class newCls = readClass(CLS, headerIsBundle, headerIsPreoptimized); . }}... // omit some code}Copy the code
Through the _read_images process, we finally look at the loading class information, get the list of classes from it, start reading the class information, and look at the function definition of readClass
readClass
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) { const char *mangledName = cls->nonlazyMangledName(); . cls->fixupBackwardDeployingStableSwift(); Class replacing = nil; if (mangledName ! = nullptr) { if (Class newCls = popFutureNamedClass(mangledName)) { ...... class_rw_t *rw = newCls->data(); const class_ro_t *old_ro = rw->ro(); memcpy(newCls, cls, sizeof(objc_class)); // Manually set address-discriminated ptrauthed fields // so that newCls gets the correct signatures. newCls->setSuperclass(cls->getSuperclass()); newCls->initIsa(cls->getIsa()); rw->set_ro((class_ro_t *)newCls->data()); newCls->setData(rw); freeIfMutable((char *)old_ro->getName()); free((void *)old_ro); addRemappedClass(cls, newCls); replacing = cls; cls = newCls; } } if (headerIsPreoptimized && ! replacing) { ...... } else {if (mangledName) {// Some Swift generic classes can lazily generate their names addNamedClass(cls, mangledName, replacing); } else { Class meta = cls->ISA(); const class_ro_t *metaRO = meta->bits.safe_ro(); . } addClassTableEntry(cls); }... return cls; }Copy the code
We can see from the readClass code that the class reads ro and assigns to RW, but does this actually happen? To verify this, use LLDB debugging. First, we need to add some code to readClass to verify that the incoming class is the object we are studying, for example LGPerson.
const char *mangledName = cls->nonlazyMangledName(); const char *LGPersonName = "LGPerson"; If (STRCMP (mangledName, LGPersonName) == 0) {// Print printf("% s-atom: to study: - %s\n",__func__,mangledName); }Copy the code
inmangledName
Add the above code, and then make a breakpoint to start debugging.According to the above running results,ro
andrw
Not inreadClass
I’m going to implement it here, and I’m just going to add tables here. thusreadClass
The main operations are as follows:
- read
class
Total table andmach-o
theclass
Table comparison - Associate the class name with the address of the class
From this point of view it is not the rW and RO flow of the class, then we will trace the related flow from LLDB debugging, continue the above study object code in the related class loading function of the _readImage method above, and break the custom print code.
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { // Realize non-lazy classes (for +load methods and static instances) for (EACH_HEADER) { classref_t const *classlist = hi->nlclslist(&count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (! cls) continue; / / -- -- -- -- -- -- -- -- -- -- - custom class began to -- -- -- -- -- -- -- -- -- -- -- -- -- -- const char * mangledName = CLS - > nonlazyMangledName (); const char *LGPersonName = "LGPerson"; If (STRCMP (mangledName, LGPersonName) == 0) {printf("%s Realize non-lazy class-atom: To investigate: - %s\n",__func__,mangledName); } / / -- -- -- -- -- -- -- -- -- -- - end of the custom class -- -- -- -- -- -- -- -- -- -- -- -- -- -- addClassTableEntry (CLS); if (cls->isSwiftStable()) { if (cls->swiftMetadataInitializer()) { _objc_fatal("Swift class %s with a metadata initializer " "is not allowed to be non-lazy", cls->nameForLogging()); } // fixme also disallow relocatable classes // We can't disallow all Swift classes because of // classes like Swift.__EmptyArrayStorage } realizeClassWithoutSwift(cls, nil); } } ts.log("IMAGE TIMES: realize non-lazy classes"); // Realize newly-resolved future classes, in case CF manipulates them if (resolvedFutureClasses) { for (i = 0; i < resolvedFutureClassCount; i++) { Class cls = resolvedFutureClasses[i]; / / -- -- -- -- -- -- -- -- -- -- - custom class began to -- -- -- -- -- -- -- -- -- -- -- -- -- -- const char * mangledName = CLS - > nonlazyMangledName (); const char *LGPersonName = "LGPerson"; If (STRCMP (mangledName, LGPersonName) == 0) {// Print printf("% s-resolvedFutureclasses-atom: - %s\n",__func__,mangledName); } / / -- -- -- -- -- -- -- -- -- -- - end of the custom class -- -- -- -- -- -- -- -- -- -- -- -- -- -- the if (CLS - > isSwiftStable ()) {_objc_fatal (" Swift, the class is not allowed to be future"); } realizeClassWithoutSwift(cls, nil); cls->setInstancesRequireRawIsaRecursively(false/*inherited*/); } free(resolvedFutureClasses); }}Copy the code
Run it again and find that the current run is not broken. Doesn’t the class load process go here? According to the question we found a paragraph above the note.
// Realize non-lazy classes (for +load methods and static instances)
Copy the code
Classes that are not lazily loaded are executed+load
The method is going to go here, and then we’re going to customizeLGPerson
In the class andload
Method, run again.This break point comes to our judgment condition, and we go down step by step, and we get torealizeClassWithoutSwift
Delta function, that’s what we’re going to focus on.
realizeClassWithoutSwift
/*********************************************************************** * realizeClassWithoutSwift * Performs first-time initialization on class cls, * including allocating its read-write data. * Does not perform any Swift-side initialization. * Returns the real class structure for the class. * Locking: runtimeLock must be write-locked by the caller **********************************************************************/ static Class realizeClassWithoutSwift(Class cls, Class previously) { runtimeLock.assertLocked(); class_rw_t *rw; Class supercls; Class metacls; . Auto ro = (const class_ro_t *) CLS ->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(); ro = cls->data()->ro(); ASSERT(! isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); } else {// here is a copy from ro to rw, class set rw rw = objc::zalloc<class_rw_t>(); rw->set_ro(ro); rw->flags = RW_REALIZED|RW_REALIZING|isMeta; cls->setData(rw); }... supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); // set class inheritance and initialize ISA CLS ->setSuperclass(supercls); cls->initClassIsa(metacls); . // Attach categories methodizeClass(cls, previously); return cls; }Copy the code
RealizeClassWithoutSwift mainly does the following:
- from
mach-o
Reading RO data - the
ro
Make a copy of your datarw
- Set up the
class
The inheritance chain - Initialize the
ISA
Walk a - The method that calls the class writes the function
According to therealizeClassWithoutSwift
therw
andro
Operation code, we run the actual project to verify.throughLLDB
Debugging can be printed herero
andrw
Information verified inrealizeClassWithoutSwift
Methods in thero
andrw
At the bottom of the current function we can see the callmethodizeClass
Function, then take a look at the source of this function.
methodizeClass
/*********************************************************************** * methodizeClass * Fixes up cls's method list, protocol list, and property list. * Attaches any outstanding categories. * Locking: runtimeLock must be held by the caller **********************************************************************/ static void methodizeClass(Class cls, Class previously) { runtimeLock.assertLocked(); bool isMeta = cls->isMetaClass(); auto rw = cls->data(); auto ro = rw->ro(); auto rwe = rw->ext(); . Method_list_t *list = ro->baseMethods(); If (list) {// Write the method name to methodLists and sort prepareMethodLists(CLS, & List, 1, YES, isBundleClass(CLS), nullptr); if (rwe) rwe->methods.attachLists(&list, 1); }... }Copy the code
MethodizeClass mainly contains the following points:
- The name of the method list that reads the class
- The method name is written to methodLists and sorted
- Access to rwe
- Get protocol list
- Load classification
MethodizeClass calls prepareMethodLists. Here is the code for the prepareMethodLists function.
static void prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount, bool baseMethods, bool methodsFromBundle, const char *why) { ...... 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*/); }}... }Copy the code
Source code implementation of fixupMethodList methods
static void fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort) { runtimeLock.assertLocked(); ASSERT(! mlist->isFixedUp()); // fixme lock less in attachMethodLists ? // dyld3 may have already uniqued, but not sorted, the list if (! mlist->isUniqued()) { mutex_locker_t lock(selLock); // Unique selectors in list. for (auto& meth : *mlist) { const char *name = sel_cname(meth.name()); meth.setName(sel_registerNameNoLock(name, bundleCopy)); } } // Sort by selector address. // Don't try to sort small lists, as they're immutable. // Don't try to sort big lists of nonstandard size, as stable_sort // won't copy the entries properly. if (sort && ! mlist->isSmallList() && mlist->entsize() == method_t::bigSize) { method_t::SortBySELAddress sorter; std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter); } // Mark method list as uniqued and sorted. // Can't mark small lists, since they're immutable. if (! mlist->isSmallList()) { mlist->setFixedUp(); }}Copy the code
We are infixupMethodList
Method to insert a printed piece of code, and then run to see the custom class information.See the custom class hereLGPerson
In the afterfixupMethodList
Method is executed, resulting in a list of methods whose addresses are sorted.
conclusion
According to the above source code and the analysis of the running results, the loading process of the class roughly has the following:
- By calling the
readImages
Read the image file of the class readClass
Read the name of the class and associate it with the classrealizeClassWithoutSwift
To deal withsuperclass
,isa
Read,ro
Data, and giverw
The assignmentmethodizeClass
Access to thero
In themethodlist
, writes the method name and sorts the method list
Wrote last
Lazy and non-lazy loading, RWE, and the methodizeClass method are also discussed in this article, which will be discussed later for space reasons.