Study in harmony! Not anxious not impatient!! I am your old friend Xiao Qinglong

We analyze in _objc_init, map_images->… – > _read_images – > readClass readClass internal:

  • Write the class name to the non-metaclass mapping table (addNamedClass)
  • Add a class to that list of all classes (addClassTableEntry)

So far, we have not found out where the ro, RW information of the class was initialized. Looking at the operations inside the _read_images function, we find that only the following parts of the code are related to classes:

  • ************ Fix confusion in class ************ (which we have already analyzed)
  • ************ fixed remapping of the ************ class that was not loaded by the image file
  • The loading of the ************ class handles ************
  • ************ implement future class resolution ************

Using helper code, locate the custom Direction class:

/******* Auxiliary code START *********/
const char *mangledName = cls->nonlazyMangledName();
const char *comName = "Direction";
if (strcmp(comName, mangledName) == 0) {
    printf("mangledName-->%s\n",mangledName);
}
/******* auxiliary code END *********/
Copy the code

Finally locate the load handling section of the class:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses){...// 8, ************ class loading processing ************. classref_tconst *classlist = hi->nlclslist(&count);
    for (i = 0; i < count; i++) {
        Class cls = remapClass(classlist[i]);
        if(! cls)continue;
        /******* Auxiliary code START *********/
        const char *mangledName = cls->nonlazyMangledName();
        const char *comName = "Direction";
        if (strcmp(comName, mangledName) == 0) {
            printf("mangledName-->%s\n",mangledName);
        }
        /******* auxiliary code END *********/. realizeClassWithoutSwift(cls, nil); . }... }Copy the code
realizeClassWithoutSwift

Enter the 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 **********************************************************************/
/** * Perform the first initialization of class CLS, * including allocating read and write data. * Do not perform any Swift side initialization. * Returns the actual class structure of the class. * Lock: runtimeLock must be written locked by the caller */
static Class realizeClassWithoutSwift(Class cls, Class previously){.../// ************ ro, rw processing ************
   auto ro = (const class_ro_t *)cls->data();
   auto isMeta = ro->flags & RO_META;
   if (ro->flags & RO_FUTURE) {
       ...
   }else{
       rw = objc::zalloc<class_rw_t>();
       rw->set_ro(ro);// ro copy a copy to rwrw->flags = RW_REALIZED|RW_REALIZING|isMeta; cls->setData(rw); }.../// class ************ processing ************

    // Load the parent class recursively
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    // Load the metaclass recursivelymetacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); .// Associate the parent class with the metaclass. That's inheritance chain and ISA.cls->setSuperclass(supercls); cls->initClassIsa(metacls); .// Associate the parent class with the child class
    if (supercls) {
        addSubclass(supercls, cls);
    } else{ addRootClass(cls); }...// Write the method name and sort the method
    methodizeClass(cls, previously);
    return cls;
}
Copy the code

After performing ro assignment, continue printing:

The run and sleep instance methods have been successfully printed. The console enters x/ 4GX CLS and enters the next recursion. Until the breakpoint is located, the console continues typing x/ 4GX CLS:

If you have identified the metaclass that is currently at the breakpoint, repeat the preceding steps to see if ro contains a class method:

At this point, the class method is also printed in the metaclass ro.

methodizeClass
static void methodizeClass(Class cls, Class previously){.../******* Auxiliary code START *********/
    const char *mangledName = cls->nonlazyMangledName();
    const char *comName = "Direction";
    if (strcmp(comName, mangledName) == 0) {
        printf("Class loading processing | | mangledName - > % s \ n",mangledName);
    }
    /******* auxiliary code END *********/bool isMeta = cls->isMetaClass(); auto rw = cls->data(); auto ro = rw->ro(); auto rwe = rw->ext(); . method_list_t *list = ro->baseMethods();if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1); }... }Copy the code

To ensure that the class we are studying is the normal class Direction, add the helper code, and debug with a breakpoint on the printf line:

Enter the prepareMethodLists:

static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle, const char *why){.../// Core part
    for (int i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[i];
        ASSERT(mlist);
        if(! mlist->isFixedUp()) { fixupMethodList(mlist, methodsFromBundle,true/*sort*/); }}... }Copy the code

Put a breakpoint in the for statement:

Note that the mlist inside the for statement is the ro list. Enter the fixupMethodList:

static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort){.../ / / set the SEL
    for (auto& meth : *mlist) {
        constchar *name = sel_cname(meth.name()); meth.setName(sel_registerNameNoLock(name, bundleCopy)); }.../// method sort
    if(sort && ! mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {method_t::SortBySELAddress sorter; std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter); }... }Copy the code

To see method sorting, add some print code to fixupMethodList:

static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort){... .// To ensure that the Direction class is currently printed,
    // The breakpoint can be blocked in methodizeClass, then clear console data, then release the breakpoint.
    for (auto& meth : *mlist) {
        const char *name = sel_cname(meth.name());
        printf("%s ->%p\n",name,meth.name());
    }
    
    /// method sort
    if(sort && ! mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {method_t::SortBySELAddress sorter;
        std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
    }
    
    printf("Sorted \n\n");
    for (auto& meth : *mlist) {
        const char *name = sel_cname(meth.name());
        printf("%s ->%p\n",name,meth.name()); }... }Copy the code

Lazy loading and non-lazy loading

A project will have more and more classes over time, and if each class reads and writes ro, RW, methodList, etc., before main starts, it will significantly speed up the application startup. So Apple designed lazy class loading and non-lazy class loading patterns. For non-lazily loaded classes, all you need to do is implement the +(void)load method inside the class. Recall the previous read_image method:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses){...// 8, ************ class loading processing ************
    // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code before
    // this thread finishes its fixups.
    // +load handled by prepare_load_methods()
    // The load method is implemented to enter the for loop. classref_tconst *classlist = hi->nlclslist(&count);
    for (i = 0; i < count; i++) { ... realizeClassWithoutSwift(cls, nil); . }... }Copy the code

The comment explains that only non-lazily loaded classes that implement the load method are executed in the for loop, which then executes the realizeClassWithoutSwift function to initialize the class. So, where does the initialization start for lazy loaded classes that do not implement the load method? To investigate this, we created a new class, DirectionChild, that does not implement the load method.

Next, we implement DirectionChild in main:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Here's the main function ~");
        DirectionChild *dir = [DirectionChild new];
        NSLog(@"This is the end of main.");
    }
    return 0;
}
Copy the code

Add a piece of secondary code to realizeClassWithoutSwift to locate the DirectionChild class under breakpoints:

/******* Auxiliary code START *********/
const char *mangledName = cls->nonlazyMangledName();
const char *comName = "DirectionChild";
if (strcmp(comName, mangledName) == 0) {
    printf("Class load handling -> I'm DirectionChild");
}
/******* auxiliary code END *********/
Copy the code

The last sentence printed by the console states that the current breakpoint has completed the steps leading up to main. If you look to the left, you can see lazy loading classesDirectionChildThe initializationrealizeClassWithoutSwiftFunction is bylookUpImpOrForwardLaunched.



Go back toObjc sourceSearch,lookUpImpOrForward

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior){... cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE); . }Copy the code
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize){... cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); . }Copy the code
static Class

realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
    return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}
Copy the code
static Class

realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked){... realizeClassWithoutSwift(cls, nil); . }Copy the code

So the general flow of lazy loading classes goes like this:

  • _objc_msgSend_uncached
  • lookUpImpOrForward
  • realizeAndInitializeIfNeeded_locked
  • realizeClassMaybeSwiftAndLeaveLocked
  • realizeClassMaybeSwiftMaybeRelock
  • realizeClassWithoutSwift

Which method was it initiated by? I’m not sure yet, it could be alloc, it could be class methods, etc. This is a one-by-one test: test alloc

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Here's the main function ~ test alloc");
        DirectionChild *dir = [DirectionChild new];
        NSLog(@"This is the end of main.");
    }
    return 0;
}
Copy the code

Test class methods

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Here's the main function ~ test the class method.");
        [DirectionChild happy];
        NSLog(@"This is the end of main.");
    }
    return 0;
}
Copy the code

Result: The DirectionChild class is initialized by calling alloc or a class method. Conclusion: Sending a message to a normal lazy-loaded class enters the class initialization process.

Lazy loading and non-lazy loading classes -> load process

Non-lazily loading classes (when map_images) :

  • _read_images
  • _getObjc2ClassList
  • readClass
  • realizeClassWithoutSwift
  • methodizeClass

Lazily loading the class (when the first message is sent to the class) :

  • _objc_msgSend_uncached
  • lookUpImpOrForward
  • realizeAndInitializeIfNeeded_locked
  • realizeClassMaybeSwiftAndLeaveLocked
  • realizeClassMaybeSwiftMaybeRelock
  • realizeClassWithoutSwift
  • methodizeClass

Classification of inquiry

Divide the structure to add classification

#import "Direction.h"
@interface Direction (category)
@property (nonatomic , strong) NSString *name_category;
@property (nonatomic , assign) NSInteger count;
- (void)eatSome;
+ (void)cleanSome;
@end


#import "Direction+category.h"
@implementation Direction (category)<NSObject>
- (void)eatSome{
    NSLog(@"Direction+category ----eat something.");
}
+ (void)cleanSome{
    NSLog(@"Direction+category ----cleanSome.");
}
@end
Copy the code

CD to the category directory, Run the $xcrun – SDK iphoneos clang-arch arm64-rewrite-objc Direction+category Open Direction+category. CPP and search for category globally to locate the category structure:

struct _category_t {
    const char *name;
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;
    const struct _method_list_t *class_methods;
    const struct _protocol_list_t *protocols;
    const struct _prop_list_t *properties;
};
Copy the code

Among them:

  • name: classification of
  • cls: refers to the class
  • instance_methods: List of instance methods
  • class_methods: List of class methods
  • protocols: Protocol list
  • properties: Property list

Note: Set and GET methods are not automatically generated for attributes in a category. If you pull the code down to the end, you can see the following code:

Corresponds to the category structure one by one.



Go back toObjc sourceSearch,category_t:Once again, it’s trueClassification is essentially a structure.

Classification loading

We’ve looked at the nature of a category. It has properties, protocols, instance methods, and class methods just like a normal class. So how does a category load into a normal class so that we can call it directly from the class? Going back to the objc source code, locate the methodizeClass function and see a section of code related to the category:

static void methodizeClass(Class cls, Class previously){... auto rwe = rw->ext(); .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);
    }
    // Attach categories.
    // Append to category
    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); }}... }Copy the code

We find that the attachLists function needs a value in RWE to execute, so we go to the RWE initialization code and go to ext

class_rw_ext_t *ext() const {
    return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}

class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
        } else {
            return extAlloc(v.get<constclass_ro_t *>(&ro_or_rw_ext)); }}// Ext returns get_ro_or_rwe, and the operation for get_ro_or_rwe is in extAllocIfNeeded
Copy the code

Search for extAllocIfNeeded and find the following places called:

  • AttachCategories function (adds the attributes, protocols, and methods of the category to the class)
  • DemangledName function
  • Class_setVersion function (class version setting)
  • AddMethods_finish function (add method)
  • Class_addProtocol function (add protocol)
  • The _class_addProperty function (adding properties)
  • Objc_duplicateClass function

The core of these functions that initialize RWE is the attachCategories function. Search it globally to see where it was called:

void attachToClass(Class cls, Class previously, int flags){...if (flags & ATTACH_CLASS_AND_METACLASS) {
        int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
        attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
        attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
    } else{ attachCategories(cls, list.array(), list.count(), flags); }... }static void load_categories_nolock(header_info *hi){... attachCategories(cls, &lc,1, ATTACH_EXISTING); . attachCategories(cls->ISA(), &lc,1, ATTACH_EXISTING | ATTACH_METACLASS); . }Copy the code

Based on the search results, only attachToClass and load_categories_nolock invoke attachCategories and then assign a value to RWE.

code

Link: pan.baidu.com/s/17bzAR9wp… Password: JO8A – from Baidu network disk share

This paper summarizes

  1. Research direction: Where is ro and RW initialized?

  2. Methods of study:

  • Using auxiliary code, locate the CLS =Direction class and read the operation inside the _read_images function

  • Locate the realizeClassWithoutSwift

  • Locate methodizeClass (method name write, method sort)

  • Rwe initialization is done by the extAllocIfNeeded function

  • Lazy-loaded and non-lazy-loaded classes (which implement the +load method) -> load process

  • Nature of classification

Next trailer:

IOS low-level analytics and the like (2) —- to be updated (category loading)