preface

In the loading principle of the class (above), we analyzed the process of _read_images, so far there are still ro and RW processing not found. But the relevant code, realizeClassWithoutSwift, is located, and this article will explore it

realizeClassWithoutSwift

  • Look at this inside the source code, there is a sense of familiarity, the original inThe condition preparation phase of a slow searchI’ve been here. This article USES theWSPersonClass to studyrealizeClassWithoutSwift.WSPersonClass is as follows:
// .h
@interface WSPerson : NSObject

@property (nonatomic.strong) NSString *name;
@property (nonatomic.strong) NSString *nickName;

+ (void)sayNB;
+ (void)sayGood;
+ (void)sayLike;
- (void)sayABC;
- (void)sayBCD;

@end

// .m
@implementation WSPerson

+ (void)load {
    NSLog("@" 🎈 🎈 🎈 % s, __func__);
}
+ (void)initialize {
    NSLog("@" 🎈 🎈 🎈 % s, __func__);
}
+ (void)sayNB {
    NSLog(@"%s", __func__);
}
+ (void)sayGood {
    NSLog(@"%s", __func__);
}
+ (void)sayLike {
    NSLog(@"%s", __func__);
}
- (void)sayABC {
    NSLog(@"%s", __func__);
}
- (void)sayBCD {
    NSLog(@"%s", __func__);
}
- (void)sayCDE {
    NSLog(@"%s", __func__);
}
- (void)sayDEF {
    NSLog(@"%s", __func__);
}
- (void)sayEFG {
    NSLog(@"%s", __func__);
}
- (void)sayFGI {
    NSLog(@"%s", __func__);
}
@end
Copy the code
  • In gorealizeClassWithoutSwift“, first to determine whether to studyWSPersonClass, you can study imitationreadClassTo write:
static Class realizeClassWithoutSwift(Class cls, Class previously)
{

    const char *mangledName = cls->nonlazyMangledName(a);const char *person = "WSPerson";
    if (strcmp(mangledName, person) == 0) {
        printf("🎈🎈🎈%s -- To study: %s\n", __func__, mangledName); }... }Copy the code
  • The breakpoint toprintfWhen the breakpoint is reached, the current usage is determinedWSPersonDo research and thenStep OverGo down

Ro and rw

Let’s go here:

auto ro = (const class_ro_t *)cls->data(a);// Clean memory Clean memory
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
    // This was a future class. rw data is already allocated.
    // The future class handles ro, rw
    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. // Copy a copy from ro to RW
    rw = objc::zalloc<class_rw_t> ();// Open rW space
    rw->set_ro(ro); // Copy ro to Rw
    rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
    cls->setData(rw); // 
}
Copy the code
  • From the firstdata()To obtainroThat isClean Memory
  • If it’s a future class, it goes inifProcessing, andWSPersonIt’s a given class, and it’s reached by the breakpointelseThis is mainly truerwTreatment, divided into three steps:
      1. Open uprwspace
      1. willroMake a copy of thisrw
      1. Will the newrwIn theclassthebitsIn the

Cache initialization

cls->cache.initializeToEmptyOrPreoptimizedInDisguise(a);// Cache initialization

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META); // If it is a metaclass, do something about the cache
#endif
Copy the code
  • Here the main cache initialization, and the metaclass cache processing

Parent class and metaclass handling

supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil); // For the parent class ro, rw processing
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); // For the metaclass class ro, rw processing

#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.
    // If metaclass isa = raw ISA
    cls->setInstancesRequireRawIsa(a); }else {
    // Disable non-pointer isa for some classes and/or platforms.
    // Set instancesRequireRawIsa.
    bool instancesRequireRawIsa = cls->instancesRequireRawIsa(a);bool rawIsaIsInherited = false;
    static bool hackedDispatch = false;

    if (DisableNonpointerIsa) {
        // Non-pointer isa disabled by environment or app SDK version
        // The environment variable OBJC_DISABLE_NONPOINTER_ISA, instancesRequireRawIsa = true is set
        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); // Set the superclass
cls->initClassIsa(metacls); // Set the metaclass.// Set the parent and root classes
if (supercls) {
    addSubclass(supercls, cls);
} else {
    addRootClass(cls);
}
Copy the code
  • It’s mostly about gettingclsAnd then setclsThe parent class of theclstheisaDirectional acquiredThe metaclass, and then setThe parent classandThe root class
  • DisableNonpointerIsaThese are the environment variables mentioned in the previous articleOBJC_DISABLE_NONPOINTER_ISAIf an environment variable or metaclass is set,isaAre allPure isa (raw isa)If it is not a metaclass and the environment variable is not setNon-pointer isa
  • So after all of these steps, let’s seeroWe know that the metaclass has the same name as the class, so we add a condition:
if (strcmp(mangledName, person) == 0) {
    if(! isMeta) {printf("🎉🎉🎉 %s -- to be studied, and not metaclass: %s\n", __func__, mangledName); }}Copy the code
  • Then, inprintfRun at breakpoint, and printroMethod:



  • althoughrothebaseMethodListIt has the address, but it doesn’t have the content, it’s just a shell, it doesn’t have the method, so let’s seemethodizeClassmethods

methodizeClass

  • Because of the need to useWSPersonTo explore the list of methods, so do the filtering here and exclude metaclasses, as above.

Analysis of the

  • The main code is as follows:



  • becausepreviouslyThe parameter is passed asnil, so I don’t need to look at the code here. The main thing is to sort the assignment of methods, and to handle the attributes and protocols of the classification

prepareMethodLists

  • Its source code is as follows



  • The main sort code isfixupMethodList, the code is as follows:



  • Here it is to iterate over the list of methods to fetch the method, and then to fetch the method name in the callsetNameMethod associates the name, which isimpandselassociated
  • callstd::stable_sortMethod to sort

Code validation

  • insortPrint the list of methods before and after:



  • The print result is as follows:



2. The order of methods in class methods is variable, so it needs to be sorted

  • After sorting, printro, found ornil:



  • Still no method list, becauserweisnilFollow the break point to continue to walk downobjc::unattachedCategories.attachToClassmethods

attachToClass

  • Its source code is as follows:
class UnattachedCategories : public ExplicitInitDenseMap<Class, category_list>
{
    ...
    void attachToClass(Class cls, Class previously, int flags)
    {
        runtimeLock.assertLocked(a);ASSERT((flags & ATTACH_CLASS) ||
               (flags & ATTACH_METACLASS) ||
               (flags & ATTACH_CLASS_AND_METACLASS));

        auto &map = get(a);auto it = map.find(previously);

        if(it ! = map.end()) {
            category_list &list = it->second;
            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);
            }
            map.erase(it); }}}Copy the code
  • The main thing here is to add the classification method to the metaclass, but the breakpoint is not reachedif (it ! = map.end())And after the walkrweIt has no value. And in the judgmentattachCategoriesIt seems to have something to do with categorization, so let’s see

attachCategories

  • Found it in the methodrweThe assignment of
auto rwe = cls->data() - >extAllocIfNeeded(a)Copy the code
  • Which is calledextAllocIfNeededMethod, which is implemented as follows:
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 *>(&ro_or_rw_ext);
    } else {
        return extAlloc(v.get<const class_ro_t*>(&ro_or_rw_ext)); }}Copy the code
  • The general idea is that if it isrweReturns, or creates if not. So the core isextAllocIfNeededWhen it was called.

Classification loading

The global search results are as follows:

    1. attachCategoriesMethod call
    1. inif (isRealized() || isFuture()), which is the future class call
    1. inclass_setVersionCalled when setting the version
    1. When the method is addedaddMethods_finishcall
    1. Add the agreementclass_addProtocolCalled when the
    1. Add attributes_class_addPropertyCalled when the
    1. inobjc_duplicateClassRepeated class calls

With these calls, the memory of WWDC2020 is verified. Rwe is only processed dynamically, that is, the classification is loaded at run time.

  • Due to the signature walked to the last entryattachCategoriesMethod, so the center of mass is in this method.

Work backward

  • searchattachCategoriesWhere to make the call, and two methods are located:load_categories_nolockandattachToClass
attachToClass
  • searchattachToClass, found only inMethodizeClassAnd bypreviouslyParameter constraint, and the signature analysis passed it asnilSo they don’t walk in
load_categories_nolock
  • Define one before you study itWSPersonThe classification of
@interface WSPerson (App)

@property (nonatomic.copy) NSString *category_hobby;
@property (nonatomic.assign) NSInteger age;

- (void)app_categoryInstanceMethod1;
- (void)app_categoryInstanceMethod2;
+ (void)app_categoryClassMethod;

@end

@implementation WSPerson (App)

- (void)app_categoryInstanceMethod1 {
    NSLog(@"%s", __func__);
}

- (void)app_categoryInstanceMethod2 {
    NSLog(@"%s", __func__);
}

+ (void)app_categoryClassMethod {
    NSLog(@"%s", __func__);
}
@end 
Copy the code
  • Then, inload_categories_nolockAdd screening inWSPersonCode, and run it, and it won’t come in. The breakpoint again inThe for loop inAnd found thatcountfor0Is it not loaded into the class? Let’s analyze it againload_categories_nolockThe call
backstepping
  • Global searchload_categories_nolock, found two calls_read_imagesandloadAllCategories“And continue the searchloadAllCategoriesIt knowload_imagesIn the call.
_read_images call

The comments called in read_images are classes that appear at startup and are found to be delayed until the first load_images call after the _dyLD_OBJC_NOTIFy_register call completes, which is generally not called.

Load_images call

Apply a breakpoint to loadAllCategories() called by load_images, and then to load_categories_nolock of loadAllCategories. You can apply a breakpoint that applies to the categories_nolock of loadAllCategories, but this still does not apply to the category list of the class. Apply the +load method to both the class and category, try again, and find that the load_categories_NOLock class filter is applied

Classification loading process
  • To sum up, the call process of classification is as follows:
    • load_images -> loadAllCategories -> load_categories_nolock -> attachCategories

Question: Why can you apply the +load method to classes and categories and apply the load_categories_NOLock method in the next article

Category

We’ve talked about categories, and the loading process for categories, so what are categories, and what are we going to explore

Nature of classification

Clang Category view

  • withclangTo viewC++Source:
static struct _category_t* _OBJC_LABEL_NONLAZY_CATEGORY_$[] = {
	&_OBJC_$_CATEGORY_WSPerson_$_App,
};
Copy the code
  • You can see it in the source code_category_tStructure, you can see it insideWSPersonThe name of the category isApp._category_tIs a structure that has the following structure:
struct _category_t {
    const char *name; // Category name
    struct _class_t *cls; / / the name of the class
    const struct _method_list_t *instance_methods; // Object methods
    const struct _method_list_t *class_methods; / / class methods
    const struct _protocol_list_t *protocols; / / agreement
    const struct _prop_list_t *properties; / / property
};
Copy the code
  • in_category_tIn the structure
    • nameIs the name of the category, in this caseApp
    • clsIs the name of the class
    • instance_methodsandclass_methodsObject methods and class methods. Why are there two methods? BecauseThere is no metaclass for classification, so there will be two variables to record object methods and instance methods, respectively.
    • protocolsIs agreement
    • propertiesIs the property
  • Keep looking down and findWSPersonthe_category_tStructure:



  • _category_tThe first argument is notAppHow could it beWSPerson, the second parameter is notWSPersonWhy is that? The compiler doesn’t know what they are, so it fills in temporary values that will be re-assigned at run time.

View the objc4-812

  • And againobjcSource check belowcategory



  • objcThe source andclangOut of theC++The code ofcategoryBasically the same structure

So it follows that the essence of classification is structure

Lazy-loaded classes and non-lazy-loaded classes

doubt

  • Explore ways to enterrealizeClassWithoutSwiftBefore, due to inWSPersonImplemented in the+ loadTo get into the loop:
// 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;

        addClassTableEntry(cls); .realizeClassWithoutSwift(cls, nil); }}Copy the code
  • Why is that? The comment says, this is a non-lazily loaded class, only implemented+loadMethod to walk inrealizeClassWithoutSwift, so get rid of+loadCan walk intorealizeClassWithoutSwift? Let’s start with comments+loadMethod, and then add it to the methodWSPersonClass identification code:
const char *mangledName = cls->nonlazyMangledName(a);const char *person = "WSPerson";
if (strcmp(mangledName, person) == 0) {
    printf("🎈🎈🎈%s -- tO study: %s -- is metaclass: %d\n", __func__, mangledName, isMeta);
}
Copy the code
  • Print again, you can also walk in, look at the stack, and see that it’s not going throughread_imagesCome in:



  • This moment suddenly realizes, does not realize+loadWhen, the method load will go to a slow lookup, that is, when the message is sent, it will loadLazy loading class

The process to summarize

  • Lazy loading class loading process:lookUpImpOrForward -> realizeAndInitializeIfNeeded_locked -> realizeClassMaybeSwiftAndLeaveLocked -> realizeClassMaybeSwiftMaybeRelock -> realizeClassWithoutSwift -> methodizeClass
  • Non-lazy loading class loading process:map_images -> map_images_nolock -> _read_images -> realizeClassWithoutSwift -> methodizeClass