We explored the readClass function in the last post, assigning only the name of the function, but not ro or rw. Here we continue to explore the remaining calls to _read_images.

The introduction of realizeClass

Since the purpose of our exploration is class loading, let’s ignore the protocol and categories areas for now. To debug the code, let’s first create a JSPerson class:

@interface JSPerson : NSObject
@property (nonatomic, strong) NSString *nickName;
​
- (void)say1;
- (void)say2;
​
+ (void)sayHappy;
@end
​
#import "JSPerson.h"
@implementation JSPerson
​
- (void)say1{
    NSLog(@"JSPerson say : %s",__func__);
}
- (void)say2{
    NSLog(@"JSPerson say : %s",__func__);
}
​
+ (void)load{
    NSLog(@"load");
}
+ (void)sayHappy{
    NSLog(@"JSPerson say : %s",__func__);
}
@end
Copy the code

Let’s move on to the _read_images function. Realize non-lazy Classes and Realize Future Classes. Let’s add our debugging code in two sections to see how our custom classes are loaded:

// 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; Const char *mangledName = CLS ->nonlazyMangledName(); const char *mangledName = CLS ->nonlazyMangledName(); const char *customerClassName = "JSPerson"; If (STRCMP (mangledName, customerClassName) == 0) {printf("%s -: non-lazy classes ") - %s\n",__func__,mangledName); } 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]; if (cls->isSwiftStable()) { _objc_fatal("Swift class is not allowed to be future"); } const char *mangledName = CLS ->nonlazyMangledName(); const char *customerClassName = "JSPerson"; If (STRCMP (mangledName, customerClassName) == 0) {printf("%s -: realize Future classes ": - %s\n",__func__,mangledName); } realizeClassWithoutSwift(cls, nil); cls->setInstancesRequireRawIsaRecursively(false/*inherited*/); } free(resolvedFutureClasses); } ts.log("IMAGE TIMES: realize future classes");Copy the code

At the break point of two printf lines, run the source program to see if it reaches the break point. _read_images -: non-lazy classes -Jsperson, the code executes to non-lazy classes, where the core code for class loading is in the realizeClassWithoutSwift function, so let’s continue exploring the realizeClassWithoutSwift function.

RealizeClassWithoutSwift analysis

Ro before operation

Auto ro = (const class_ro_t) CLS ->data(); Since ro is the data in clean Memory, we are sensitive to it, so we chart the location of the break point:

Use the LLDB debugger to view the ro information:

(lldb) p ro
(const class_ro_t *) $0 = 0x00000001000080c0
(lldb) p *$0
(const class_ro_t) $1 = {
  flags = 0
  instanceStart = 8
  instanceSize = 16
  reserved = 0
   = {
    ivarLayout = 0x0000000000000000
    nonMetaclass = nil
  }
  name = {
    std::__1::atomic<const char *> = "JSPerson" {
      Value = 0x0000000100003db0 "JSPerson"
    }
  }
  baseMethodList = 0x0000000100008108
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100008170
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008198
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $1.baseMethodList
(void *const) $2 = 0x0000000100008108
(lldb) p *$2
(lldb) 
Copy the code

By printing we find that the baseMethodList in ro is empty at this point. It’s not clear when this was assigned, so let’s continue exploring.

The assignment of rw

Next comes the assignment to RW, which is dirty Memory

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 { // 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); }Copy the code

Isa and superClass assignments

The following code is the assignment to isa and superClass:

supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); #if SUPPORT_NONPOINTER_ISA if (isMeta) { // Metaclasses do not need any features from non pointer ISA // This allows for A faspath for classes in objc_retain objc_release. / / / metaclass not non pointer ISA CLS - > setInstancesRequireRawIsa (); } else { // Disable non-pointer isa for some classes and/or platforms. // Set instancesRequireRawIsa. bool instancesRequireRawIsa = cls->instancesRequireRawIsa(); bool rawIsaIsInherited = false; static bool hackedDispatch = false; If (DisableNonpointerIsa) {// ISA is pure ISA if we set the variable without using non pointer // Non-pointer ISA disabled by environment or APP SDK  version instancesRequireRawIsa = true; } else if (! hackedDispatch && 0 == strcmp(ro->getName(), "OS_object")) { // hack for libdispatch et al - isa also acts as vtable pointer hackedDispatch = true; instancesRequireRawIsa = true; } else if (supercls && supercls->getSuperclass() && supercls->instancesRequireRawIsa()) { // This is also propagated by addSubclass() // but nonpointer isa setup needs it earlier. // Special case: instancesRequireRawIsa does not propagate // from root class to root metaclass instancesRequireRawIsa = true; rawIsaIsInherited = true; } if (instancesRequireRawIsa) { cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited); } } // SUPPORT_NONPOINTER_ISA #endif // Update superclass and metaclass in case of remapping cls->setSuperclass(supercls); cls->initClassIsa(metacls);Copy the code

The realizeClassWithoutSwift function ends with a call to methodizeClass, which we explore in the next section.

MethodizeClass analysis

MethodizeClass is, by definition, the treatment of the method.

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(); // Methodizing for the first time if (PrintConnecting) { _objc_inform("CLASS: methodizing class '%s' %s", cls->nameForLogging(), isMeta ? "(meta)" : ""); } const char *mangledName = CLS ->nonlazyMangledName(); const char *customerClassName = "JSPerson"; If (STRCMP (mangledName, customerClassName) == 0) {// Print the class name if (! IsMeta) {printf("%s -: non-lazy classes: -% s\n",__func__,mangledName); }} // Install methods and properties that the class implements itself.method_list_t *list = ro->baseMethods(); if (list) { prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr); 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); } // Root classes get bonus method implementations if they don't have // them already. These apply before category replacements. 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 { // When a class relocates, categories with class methods // may be registered on the class itself rather than on // the metaclass. Tell attachToClass to look for those. 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 ? '+' : '-', cls->nameForLogging(), sel_getName(meth.name())); } ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name()); } #endif }Copy the code

The breakpoint enters the diagram position while the method list still cannot be printed

prepareMethodLists

So let’s go ahead and do the prepareMethodLists function.

static void prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount, bool baseMethods, bool methodsFromBundle, const char *why) { runtimeLock.assertLocked(); if (addedCount == 0) return; // There exist RR/AWZ/Core special cases for some class's base methods. // But this code should never need to scan base methods for RR/AWZ/Core: // default RR/AWZ/Core cannot be set before setInitialized(). // Therefore we need not handle any special cases here. if  (baseMethods) { ASSERT(cls->hasCustomAWZ() && cls->hasCustomRR() && cls->hasCustomCore()); } else if (cls->cache.isConstantOptimizedCache()) { cls->setDisallowPreoptCachesRecursively(why); } else if (cls->allowsPreoptInlinedSels()) { #if CONFIG_USE_PREOPT_CACHES SEL *sels = (SEL *)objc_opt_offsets[OBJC_OPT_INLINED_METHODS_START]; SEL *sels_end = (SEL *)objc_opt_offsets[OBJC_OPT_INLINED_METHODS_END]; if (method_lists_contains_any(addedLists, addedLists + addedCount, sels, sels_end - sels)) { cls->setDisallowPreoptInlinedSelsRecursively(why); } #endif } // Add method lists to array. // Reallocate un-fixed method lists. // The new methods are PREPENDED to the method list array. 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 the class is initialized, then scan for method implementations // tracked by the class's flags. If it's not initialized yet, // then objc_class::setInitialized() will take care of it. if (cls->isInitialized()) { objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount); objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount); objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount); }}Copy the code

The core call is the fixupMethodList function.

fixupMethodList

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

The core code is stable_sort. We print a list of sorting methods before and after, as shown in the diagram

Note: here must be in realizeClassWithoutSwift to determine is we want to study the JSPerson class, and then see the print results, otherwise the system class will also have a lot of printing, affecting our analysis.

MethodizeClass -: non-lazy classes Classes to study: -jsperson ****************sort before: say1-0x100003Dda sort before: Say2-0x100003ddf sort: nickName - 0x7fff73fb8a1c sort: setNickName: -0x7fff73Fb8362 ****************sort: Say1-0x100003dda sort: say2-0x100003DDF sort: setNickName: -0x7fff73FB8362 sort: nickName - 0x7ffF73fB8a1cCopy the code

Print the result through the above:

  • Before ordering:say1 - 0x100003dda,say2 - 0x100003ddf,nickName - 0x7fff73fb8a1c,setNickName: - 0x7fff73fb8362
  • After the order:say1 - 0x100003dda,say2 - 0x100003ddf,setNickName: - 0x7fff73fb8362,nickName - 0x7fff73fb8a1c
  • Sorting is sorted by address from lowest to highest.

summary

So far, the class loading process is: _read_images->realizeClassWithoutSwift->methodizeClass->prepareMethodLists->fixupMethodList.

Lazy-loaded classes and non-lazy-loaded classes

The classes we explored earlier are actually non-lazily loaded, and the difference between a lazily loaded class and a non-lazily loaded class is whether or not the load method is implemented.

The lazy loading

From the above analysis, we have been very clear, is in_objc_initMethod, which is when the program starts. Is that whyloadThere are too many methods, it will affect our applicationstartup.

Lazy loading class

becauseNon-lazily loaded classesIt’s inefficient. It affects our startup speed. SoLazy loading classWhen was it loaded? We deleteJSPersonOf the classloadMethod, and then inmainFunction to instantiate oneJSPersonThe instance

int main(int argc, const char * argv[]) { @autoreleasepool { JSPerson *p = [JSPerson alloc]; [p say1]; NSLog(@"Hello, World!" ); } return 0; }Copy the code

We started withmainMethod to execute the program. Go to themainFunction, and then inrealizeClassWithoutSwiftAdd breakpoints:

Break point walked in and webtPrint call stack information:

The discovery call is fromlookUpImpOrForwardStart.

So our conclusion is thatLazily loaded classesIs loaded when it is first used.

conclusion

  • Non-lazily loaded classes: the program is loaded at runtime,_read_images->realizeClassWithoutSwift->methodizeClass->prepareMethodLists->fixupMethodList.

  • Lazily loaded classes: loaded on first use,lookUpImpOrForward->realizeClassWithoutSwift->methodizeClass->prepareMethodLists->fixupMethodList

    We write it all the time in developmentclassification, when it was loaded and the process of loading will be explored in the next article.