What can be gained from this article

  • How do I find the entry to perform the RW, RO, RWT operations of class?
  • Why doesn’t the breakpoint break just by adding a print? And why are other breakpoints allowed to enter?
  • If I simply don’t add the load method, when will the instance and class methods of this class be loaded?
  • Set up a complete class RW data source
  • Set the parent to the metaclass pointing (isa pointing chain) source code
  • The flow chart of realizeClassWithoutSwift
  • How do I validate the complete loading process by adding test code to the source code
  • How do I find methodList in the ro data of the currently instantiated FFPerson and FFPerson metaclass
  • Methods the sorting
  • How classes are loaded
  • Solve problems that arise during exploration

To explore an overview

How do I find the entry to perform the RW, RO, RWT operations of class

  • Debug the main line
    • In the most direct way:Set breakpoint + write print code.Dynamic debugging
    • I only care about my own customClass (FFPerson)The operation of the
    • Edge of OBTake a look at the test codeThe implementation of
  • Query monitoring point (for all class operations in _read_images)
    • Realize non-lazy classes
    • realize future classes
  • Find your goal. Where do you do itRw, RO, rWE of classThe operation of the

Inserted test code:

const char *mangledName = cls->nonlazyMangledName(a);const char *bblvPersonName = "FFPerson";
if (strcmp(mangledName, bblvPersonName) == 0) {
    printf("%s -- BBLv -- %s\n",__func__,mangledName);
}
Copy the code

Graphic interpretation

  • Respectively forLine 3770,Line 3785,Line 3799A debug breakpoint is set
  • Green shadowCode that is actively added inside (not the underlying original code)
  • Red boxThis is the division of the region

Case code

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!"); FFPerson *objc = [FFPerson alloc]; }}Copy the code

Debugging result:

2021-07-16 14:15:19.872310+0800 KCObjcBuild[2974:114580] Program ended with exit code: 0Copy the code

Without any of my custom prints, I didn’t hit a breakpoint as expected. With what same, xing excited courageously for the first time, but can not find the entrance, vexed as hell…….

Question point 1: just add a print, why does the breakpoint not break? And why are other breakpoints allowed to enter?

Adjust case code

@implementation FFPerson

+ (void)load {
    NSLog(@"%s",__func__);
}
@end
Copy the code

New debug results

Realize non-lazy classes -- _read_images -- BBLv -- FFPerson 2021-07-16 14:38:59.313671+0800 KCObjcBuild[3105:124123] +[FFPerson Load] 2021-07-16 14:38:59.314300+0800 KCObjcBuild[3105:124123]Copy the code

Conclusion: Finally found the entry point in Realize non-lazy classes, and then proceeded down the realizeClassWithoutSwift function. Finally happy to play down, to the deeper set out.

Question # 2: If I simply don’t add the load method, when will the instance and class methods of this class load?

RealizeClassWithoutSwift (class implementation)

Source:

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * for the first class CLS initialization, * including the distribution of the read and write data. * No Swift side initialization is performed. * Returns the true class structure of the class. * lock: runtimeLock must be written by the caller to lock in * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked(a);// Insert the test code, in order to specify the current class is FFPerson, for the class created and added methods, easy to explore
    const char *mangledName = cls->nonlazyMangledName(a);const char *bblvPersonName = "FFPerson";
    if (strcmp(mangledName, bblvPersonName) == 0) {
        printf("Realize non-lazy classes -- %s -- BBLv -- %s\n",__func__,mangledName);
    }

    // Create rW, superclass, metacls
    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if(! cls)return nil;
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?
    
    // Get the RO data in the class
    auto ro = (const class_ro_t *)cls->data(a);auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // Metaclass: rW data has been written
        rw = cls->data(a); ro = cls->data() - >ro(a);ASSERT(! isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // class: 1, open up rW space
        rw = objc::zalloc<class_rw_t> ();// write ro to rw
        rw->set_ro(ro);
        //3, flags flag set, metaclass 1, non-metaclass 0
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        // set rW data
        cls->setData(rw);
    }

    cls->cache.initializeToEmptyOrPreoptimizedInDisguise(a);#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // Select an index for this class.
    // If no more indexes are available for the index, set CLS ->instancesRequireRawIsa
    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 parent classes and metaclasses, if not already implemented.
    // This needs to be done after setting RW_REALIZED above, for the root class.
    // This needs to be done after selecting the class index, for the root metaclass.
    // This assumes that none of these classes has Swift content, or that the Swift initializer has already been called.
    // Fixed if we added support for ObjC subclasses of the Swift class, the assumption would be wrong.
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // The metaclass does not need any features from non-pointer ISA
        // This allows faspath of classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa(a); }else {
        // Disable non-pointer ISA for some classes and/or platforms.
        // Set instance RequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa(a);bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;
        
        // You can control DisableNonpointerIsa by setting environment variables
        if (DisableNonpointerIsa) {
            // Environment or application SDK version disables non-pointer ISA
            instancesRequireRawIsa = true;
        }
        else if(! hackedDispatch &&0= =strcmp(ro->getName(), "OS_object"))
        {
            Hack-isa also acts as a vtable pointer to libdispatch and others
            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 the parent and metaclass in case of remapping
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    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 objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to the subclass list of its FU class
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    methodizeClass(cls, previously);

    return cls;
}
Copy the code

Key point 1: Set the complete CLASS RW data

// Get the RO data in the class
auto ro = (const class_ro_t *)cls->data(a);// the metaclass identifier
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
    // Metaclass: rW data has been written
    rw = cls->data(a); ro = cls->data() - >ro(a);ASSERT(! isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
    // class: 1, open up rW space
    rw = objc::zalloc<class_rw_t> ();// write ro to rw
    rw->set_ro(ro);
    //3, flags flag set, metaclass 1, non-metaclass 0
    rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
    // set rW data
    cls->setData(rw);
}
Copy the code

Key 2: Set the parent to the metaclass pointing (isa pointing chain)

supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
Copy the code

The flow chart of realizeClassWithoutSwift

Run the source code and experience the process for real

Phase one: recursive process

Through the interpretation of the source code of realizeClassWithoutSwift and the detailed description of the flow chart, the core part of the class loading process is basically the setting of RW data, but this involves the pointing chain relation of ISA. When setting the current class FFPerson, Both superClass and metaClass are set, and the metaClass ISA points to RootMetaClass.

Execution steps:

  1. The current CLS object is FFPerson, the current FFPerson is read by _read_images, and the RW data of FFPerson is set by calling realizeClassWithoutSwift

  2. The current CLS object is FFPerson, Supercls = realizeClassWithoutSwift(remapClass(CLS ->getSuperclass()), nil); That is, call this method here to set FFPerson’s parent class NSObject, but at this point the parent class NSObject is already an instance class, that is, CLS ->isRealized determines true and returns CLS directly, without triggering superclass and metaclass again

  3. RealizeClassWithoutSwift metacls = realizeClassWithoutSwift(remapClass(CLS ->ISA()), nil); That is, call this method here to set the metaclass of FFPerson

  4. The current CLS object is FFPerson(Metaclass), Supercls = realizeClassWithoutSwift(remapClass(CLS ->getSuperclass()), nil); That is, call this method here to set the parent of the metaclass of FFPerson, which is the metaclass of NSObject, which is the root metaclass, but at this point it’s an instance of the class CLS ->isRealized and say true, return CLS, Superclass and Metaclass will not be triggered again

  5. The current CLS object is FFPerson(metaclass),metacls = realizeClassWithoutSwift(remapClass(CLS ->ISA()), nil); If this method is true, CLS ->isRealized will return CLS directly. Superclass and metaclass will not be triggered again. At this point, isa reference relation is complete.

Simplify the process

FFPerson -> NSObject -> FFPerson(metaclass) -> NSObject(metaClass) -> RootMetaClass

Test the ISA relationship of the code

FFperson -> metaclass -> rootMetaClass

Test code inheritance relationships

FFPerson -> NSObject

Test code chain diagrams

The first time you set up the RW of FFPerson, you are ready to instantiate FFPerson

The second time I go into this method to instantiate FFPerson’s parent class, NSObject, but NSObject is already instantiated, so I’m going to return, and I’m not going to do any of the rW setting logic

Enter this method a third time to prepare the metaclass of instance FFPerson, which is not initialized.

The fourth time you enter this method instance, the parent of the FFPerson metaclass, the NSObject metaclass, is instantiated and returns directly

Enter the method RootMetaClass for the fifth time

A one-stage conclusion can be drawn from the above process: FFPerson and FFPerson metaclass have not been instantiated in this recursive process, and other metaclass have been instantiated.

Phase 2: Find methodList in ro data of the currently instantiated FFPerson and FFPerson metaclass

Case code:

@interface FFPerson : NSObject
// Instance method
- (void)likeFood;
- (void)likeLive;
- (void)likeSleep;
- (void)likeGirls;
/ / class methods
+ (void)enjoyLife;
@end
**@implementation FFPerson
/ / class methods
+ (void)load {
    NSLog(@"%s", __func__);
}
+ (void)enjoyLife {
    NSLog(@"%s", __func__);
}
// Instance method
- (void)likeFood {
    NSLog(@"%s", __func__);
}
- (void)likeLive {
    NSLog(@"%s", __func__);
}
- (void)likeSleep {
    NSLog(@"%s", __func__);
}
- (void)likeGirls{
    NSLog(@"%s", __func__);
}
@end
Copy the code

FFPerson

FFPerson (metaClass)

Methods the sorting

Function call flow

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);method_list_t *list = ro->baseMethods(a);if (list) {
        // Prepare the list of methods: to sort
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }
Copy the code

prepareMethodLists

Preparation Method List

static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle, const char *why)
{
    runtimeLock.assertLocked(a);// Add the list of methods to the array.
    // Reallocate the list of unfixed methods.
    // The new method is pre-added 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*/); }}Copy the code

fixupMethodList

Fix the method list, that is, reorder it

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);
    
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            meth.setName(sel_registerNameNoLock(name, bundleCopy)); }}// sort by selector address.
    // Do not try to sort small lists because they are immutable.
    // Do not attempt to sort large lists of non-standard sizes, such as stable_sort
    // Entries will not be copied correctly.
    if(sort && ! mlist->isSmallList() && mlist->entsize() = =method_t::bigSize) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(&mlist->begin() - >big(), &mlist->end() - >big(), sorter);
    }
    
    // Mark the list of methods as unique and sorted.
    // Small lists cannot be marked because they are immutable.
    if(! mlist->isSmallList()) {
        mlist->setFixedUp();
    }
}
Copy the code

Add custom print to fixupMethodList to see if the function really does a sort

Revamped fixupMethodList

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);
    
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            printf("Before sort -> %s - %p",name,meth.name());
            meth.setName(sel_registerNameNoLock(name, bundleCopy)); }}// sort by selector address.
    // Do not try to sort small lists because they are immutable.
    // Do not attempt to sort large lists of non-standard sizes, such as stable_sort
    // Entries will not be copied correctly.
    if(sort && ! mlist->isSmallList() && mlist->entsize() = =method_t::bigSize) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(&mlist->begin() - >big(), &mlist->end() - >big(), sorter);
    }
    
    for (auto& meth : *mlist) {
        const char *name = sel_cname(meth.name());
        printf("After sorting -> %s - %p",name,meth.name());
    }
    
    // Mark the list of methods as unique and sorted.
    // Small lists cannot be marked because they are immutable.
    if(! mlist->isSmallList()) {
        mlist->setFixedUp();
    }
}
Copy the code

Console output

FFPerson

Realize non-lazy classes -- _read_images -- BBLv -- FFPerson -- > likeFood-0x100003f6a -- > likeLive -- 0x100003f73 Before sort -> Likesleep-0x100003F7C Before sort -> LikeGirls-0x100003F86 Before sort -> AA_V587-0x100003F90 Before sort -> SAY_1-0x100003F98 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > after sorting likeFood 0 x100003f6a sorted - > likeLive 0 x100003f73 sorted - > likeSleep - 0x100003F7C After sort -> likeGirls - 0x100003F86 After sort -> AA_v587-0x100003F90 after sort -> say_1-0x100003F98 (LLDB)Copy the code

The metaClass FFPerson

Realize non-lazy classes -- _read_images -- BBLv -- FFPerson -- > load - 0x100003f6a -- > enjoyLife - 0x100003f6f -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > after sorting enjoyLife 0 x100003f6f sorted - > load - 0 x7fff7b9b1ea0 (LLDB)Copy the code

Conclusion:

  • The FFPerson class does not actually observe a change in order because the address is already sorted. To verify this, two more functions, AA_V587 and say_1, do not change

  • The FFPerson metaClass is sorted, verifying the sorting functionality of the fixupMethodList function

How classes are loaded

Solve problems that arise during exploration

Question point 1: just add a print, why does the breakpoint not break? And why are other breakpoints allowed to enter?

Because it is the first time, so reckless, not careful. // Realize non-lazy classes (for +load methods and static instances); // Realize non-lazy classes (for +load methods and static instances); For load methods and static instances, now it’s clear, just add a load method to the FFPerson class and you’re done.

@implementation FFPerson

+ (void)load {
    NSLog(@"%s",__func__);
}
@end
Copy the code

Question # 2: If I simply don’t add the load method, when will the instance and class methods of this class load?

Non-lazy-loaded classes: Whether or not the current class, FFPerson, implements the load method determines whether or not the class is lazy-loaded. All of the above arguments are for the load case, that is, non-lazily loaded classes.

Lazy loading: If it is a lazy loading class, there is no action during the dyLD loading phase, all the data is loaded after the main function, when FFPerson sends the first message, the data is loaded. The function call procedure is printed through BT and the method load is verified as a slow lookup process, lookUpImpOrForward.