Welcome to the iOS Exploration series.

  • IOS explores the alloc process
  • IOS explores memory alignment &malloc source code
  • IOS explores ISA initialization & pointing analysis
  • IOS exploration class structure analysis
  • IOS explores cache_T analysis
  • IOS explores the nature of methods and the method finding process
  • IOS explores dynamic method resolution and message forwarding mechanisms
  • IOS explores the dyLD loading process briefly
  • The loading process of iOS explore classes
  • IOS explores the loading process of classification and class extension
  • IOS explores isa interview question analysis
  • IOS Explore Runtime interview question analysis
  • IOS explores KVC principles and customization
  • IOS explores KVO principles and customizations
  • IOS explores the principle of multithreading
  • IOS explores multi-threaded GCD applications
  • IOS explores multithreaded GCD underlying analysis
  • IOS explores NSOperation for multithreading
  • IOS explores multi-threaded interview question analysis
  • IOS Explore the locks in iOS
  • IOS explores the full range of blocks to read

Writing in the front

After analyzing the loading process of classes in iOS, this article will take a closer look at how classes are loaded

(Please have some understanding of the class loading process before starting this article.)

First, classification preliminary exploration

1. Output CPP clang

Create a new category for FXPerson, FXPerson-FX

The terminal outputs CPP using CLANG

clang -rewrite-objc FXPerson+FX.m -o cate.cpp
Copy the code

2. Low-level analysis

Starting at the bottom of the CPP file, we first see that the classification is stored in the __objc_catList of the __DATA section of the MachO file

static struct _category_t *L_OBJC_LABEL_CATEGORY_$[1] __attribute__((used.section(" __DATA, __objc_catlist.regular.no_dead_strip"))) = {
	&_OBJC_$_CATEGORY_FXPerson_$_FX,
};
Copy the code

Second, you can see the structure of the FXPerson classification

static struct _category_t _OBJC_The $_CATEGORY_FXPerson_The $_FX __attribute__ ((used.section(" __DATA, __objc_const"))) = 
{
	"FXPerson".0.// &OBJC_CLASS_$_FXPerson,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_FXPerson_$_FX,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_FXPerson_$_FX,
	0,
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_FXPerson_$_FX,
};
Copy the code

Go to the objC source code and search category_t to see the structure of the underlying category (_category_t search failed)

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t* _classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
Copy the code

According to the FXPerson classification structure and the underlying classification structure comparison:

  • name: The class name, not the class name
  • clsClass object:
  • instanceMethods: instance methods stored on a classification
  • classMethods: Class methods stored on a classification
  • protocols: Protocol implemented by classification
  • instanceProperties: Instance properties defined by a class, but we generally add attributes to a class by associating objects
  • _classProperties: Class attributes defined by the classification

Why do classified methods keep instance methods and class methods separate?

Because classes and metaclasses are constantly compiled before, instance methods exist in classes, and class methods exist in metaclasses, where their methods belong has been determined; The categories were added later

Second, the loading of classification

From the last article we know that classes are divided into lazy loading classes and non-lazy loading classes, their loading timing is different, so what is the classification? Let’s explore them in turn

1. Lazy classes and lazy classes

Classes and classes do not implement the +load method

A lazy-loaded class is known to be tired and only loaded when it is called to send a message

Add categories in two places: _read_images and methodizeClass

// Discover categories.
// Discover and process all categories
for (EACH_HEADER) {
    // The outer loop iterates through the current class to find the Category array corresponding to the class
    category_t **catlist = 
        _getObjc2CategoryList(hi, &count);
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    for (i = 0; i < count; i++) {
        // The inner loop iterates through all categories of the current class
        category_t *cat = catlist[i];
        Class cls = remapClass(cat->cls);
        
        // First, register a Category with the class to which it belongs. If the class is already implemented, the list of methods for the class is reconstructed.
        bool classExists = NO;
        if (cat->instanceMethods ||  cat->protocols  
            ||  cat->instanceProperties) 
        {
            // Add a Category to the Class's value, which is an array of all categories corresponding to the Class
            addUnattachedCategoryForClass(cat, cls, hi);
            // Add method, protocol, and property for the Category to the Class
            if (cls->isRealized()) {
                remethodizeClass(cls);
                classExists = YES;
            }
            if (PrintConnecting) {
                _objc_inform("CLASS: found category -%s(%s) %s", 
                             cls->nameForLogging(), cat->name, 
                             classExists ? "on existing class" : ""); }}// This is the same logic as above except that this is the same logic as above except that this is the same logic as above except that this is the same logic as above
        // According to the following logic, it is possible to add a Category to the original class from a code point of view
        if (cat->classMethods  ||  cat->protocols  
            ||  (hasClassProperties && cat->_classProperties)) 
        {
            addUnattachedCategoryForClass(cat, cls->ISA(), hi);
            if (cls->ISA()->isRealized()) {
                remethodizeClass(cls->ISA());
            }
            if (PrintConnecting) {
                _objc_inform("CLASS: found category +%s(%s)", cls->nameForLogging(), cat->name); }}}}Copy the code
static void methodizeClass(Class cls)
{...// Attach categories.
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/); . }Copy the code

To avoid the other class calls to _read_images and methodizeClass (red box) being difficult to debug, add some code to each (green box) to better explore FXPerson’s classes and categories

From the function call stack on the left:

  1. toLazy loading classSend a message,lookupOrForward->realizeClassMaybeSwiftAndLeaveLocked
  2. realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwiftStart loading memory
  3. methodizeClassHandle superclass, metaclass relationship, call print twice
  4. unattachedCategoriesForClassreturnNULL
  5. another_read_imagesThe load class is not called

Accidentally overturned, first change the non-lazy loading class and lazy loading classification study it

2. Non-lazy loading classes and lazy loading classes

Only the class implements the +load method

① Same research method, run the project

Dabble in the dyLD loading process
Class loading process

  1. Program starteddyld->libSystem_initializer->libdispatch_init->_os_object_init
  2. _objc_init->map_images->map_images_nolock->_read_images
  3. realizeClassWithoutSwift->methodizeClassLoad the class into memory
  4. methodizeClassHandle superclass, metaclass relationship, call print twice
  5. unattachedCategoriesForClassreturnNULL
  6. another_read_imagesThe load class is not called

② It’s the same with lazy loading classes and lazy loading classes… Continue exploring RW (Structural analysis of reading Classes without understanding)

(lldb) p/x cls
(Class) $0 = 0x0000000100001188
(lldb) p (class_data_bits_t *)0x00000001000011a8
(class_data_bits_t *) The $1 = 0x00000001000011a8
(lldb) p The $1->data()
(class_rw_t *) $2 = 0x0000000103200060
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  version = 7
  ro = 0x00000001000010e8
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x00000001000010b0
        arrayAndFlag = 4294971568
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff9383faa0
  demangledName = 0x0000000000000000
}
(lldb) p $3.methods
(method_array_t) $4 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x00000001000010b0
      arrayAndFlag = 4294971568
    }
  }
}
(lldb) p $4.list
(method_list_t *) A $5 = 0x00000001000010b0
(lldb) p *A $5
(method_list_t) $6 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 2
    first = {
      name = "cate_doClass"
      types = 0x0000000100000faa "v16@0:8"
      imp = 0x0000000100000e00 (objc-debug`+[FXPerson(FX) cate_doClass] at FXPerson+FX.m:23)
    }
  }
}
(lldb) p A $5->get(0)
(method_t) $7 = {
  name = "cate_doClass"
  types = 0x0000000100000faa "v16@0:8"
  imp = 0x0000000100000e00 (objc-debug`+[FXPerson(FX) cate_doClass] at FXPerson+FX.m:23)
}
(lldb) p A $5->get(1)
(method_t) $8 = {
  name = "load"
  types = 0x0000000100000faa "v16@0:8"
  imp = 0x0000000100000e90 (objc-debug`+[FXPerson load] at FXPerson.m:12)
}
(lldb) 
Copy the code

The first call handles the metaclass relationship metacls = realizeClassWithoutSwift(remapClass(CLS ->ISA()));

The metaclass stores the +load method of the FXPerson class and the +cate_doClass method of the FXPerson classification

-cate_doInstance

Instructions before methodizeClass unattachedCategoriesForClass has taken the classification method of load to the class

(3) Put the debug code before the methods of rW, and find that the methods are not assigned

(lldb) p *A $5
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory methodizeClass: class name: fxPerson-0x100001188Copy the code

After the breakpoint comes to the methods assignment, the classified methods are already lying in it

Method_list_t *list = ro->baseMethods() this step just puts ro->baseMethods into rW

(4) Run the project again, print ro at the first breakpoint, the classification method already exists…

Conclusion: The lazy-loaded classification is determined at compile time for both lazy-loaded and non-lazy-loaded classes

3. Non-lazy loading classes and non-lazy loading classes

Both classes and categories implement the +load method

Revert to the original debug code and run the project

MethodizeClass (methodizeClass

Two unattachedCategoriesForClass return is NULL

② The breakpoint then goes to Discover categories of _read_images and calls them in the order shown in the figure

  • addUnattachedCategoryForClassMake an association mapping between classes/metaclasses and categories
  • callremethodizeClasscallattachCategoriesDeal with classification
static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats); }}Copy the code

RemethodizeClass calls attachCategories to process the categories

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if(! cats)return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t* *)malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t* *)malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t* *)malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        auto& entry = cats->list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if(protolist) { protolists[protocount++] = protolist; }}auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}
Copy the code

AttachCategories analysis:

  • (Suppose both classifications are realized+load)while (i--)“Is not what the Internet saysCompile SourcesIt’s loaded in reverse order. It’s loaded firstFXPerson+FXAnd as far as I’m concerned, it’s probably written for convenience
  • callattachListsAdd classification methods, attributes, protocols (Class loading processMore details)
    • memmoveMove the original data to the end
    • memcpyCopy the new data to the starting location
  • The methods of the classification do not replace the methods that the class already has, that is, if both the classification and the class havedoInstanceAfter the class is attached, there will be two methods in the class’s listdoInstance
  • The classified method is placed first in the list of new methods, and the class method is placed second in the list of new methods. This is why we usually say that the classified method “overwrites” the method of the same name, becauseThe runtimeinTo find the wayIs searched in the order of the list of methods, as long as it finds the corresponding name of the method, it will returnimpBut there may be another method with the same name behind

4. Lazy and non-lazy classes

Only classes implement the +load method

(1) First break to _read_images Discover categories, not remethodizeClass

(2) the breakpoint to methodizeClass, finally through unattachedCategoriesForClass take to value this time, and then through attachCategories added

load_images
prepare_load_methods
realizeClassWithoutSwift
methodizeClass

5. Summary of class and classification loading

  • Lazy loading class + lazy loading classification

    • Class loading inThe first message is sentWhile the classification load is inCompile time
  • Non-lazy loading class + lazy loading class

    • Class loading in_read_images, the loading of classification is inCompile time
  • Non-lazy loading class + non-lazy loading class

    • Class loading in_read_imagesThe class is loaded after the class is loadedreMethodizeClass
  • Lazy loading class + non-lazy loading class

    • Class loading inload_imagesThe class is loaded after the class is loadedmethodizeClass

6. Disputes over the namesake methods of classes and classifications

  • When a class and classification method have the same name, they must respond to the classification method (regardless of whether the class and classification are implemented or not)+load)
  • Class with the same name as more than one classification method
    • If the classification is not implemented+loadMethod, then the responseCompile SourcesThe last category
    • If one of them is implemented+loadAnd the responseNon-lazy load classification– becauseLazy load classificationIt’s already loaded into memory at compile time, andNon-lazy load classificationOnly loaded at runtime
    • If both are implemented+loadAnd the responseCompile SourcesThe last category

Third, the loading of class extension

Class extension Extension is also called an anonymous classification to add attributes and methods to the current class

There are two forms:

  • Directly in the.mNew class extension in file
  • New class extension.hfile

1. Loading of class extensions

The data comes to _read_image very early on, which is exactly what we do when we’re working with classes

But when you think about it, you already have a method implementation in the class, and do_hExtension is not enough

You can verify this by looking at the setter and getter methods for the property

From the figure above, it can be concluded that:

  • Class extensions are compiled as part of the class at compile time
  • Class extension reads ro directly at read time

2. Details of class expansion

Class extensions are not compiled into memory if they are not referenced (#import)

Four, load_image

In the previous article, it was mentioned that dyLD initializing image would trigger load_image. In the case of lazy loading class and non-lazy loading class, load_image is in the call stack when the class is loaded into memory, so we will explore in this case

When I press a breakpoint on the load_image implementation, I find that neither the class nor the class prints the +load method — load_image precedes the +load method

  • Discover the load the methods –prepare_load_methods
  • Call + load the methods –call_load_methods

1.prepare_load_methods

Discover and prepare the +load method

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();

    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if(! cls)continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods"); } realizeClassWithoutSwift(cls); assert(cls->ISA()->isRealized()); add_category_to_loadable_list(cat); }}/*********************************************************************** * prepare_load_methods * Schedule +load for classes in this image, any un-+load-ed * superclasses in other images, and any categories in this image. **********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
    if(! cls)return;
    assert(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

/*********************************************************************** * add_class_to_loadable_list * Class cls has just become connected. Schedule it for +load if * it implements a +load method. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if(! method)return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}
Copy the code

Prepare_load_methods analysis:

  • through_getObjc2NonlazyClassListTo obtainNon-lazy-loaded classesThe list of
  • throughschedule_class_loadWalk through these classes
    • The recursive call traverses the parent class+loadMethod to ensure that the parent class+loadThe method order precedes the subclass
    • calladd_class_to_loadable_listThe class of+loadmethodsloadable_classesinside
  • call_getObjc2NonlazyCategoryListTake out theNon-lazy load classificationThe list of
  • Iterate over the category list
    • throughrealizeClassWithoutSwiftTo prevent the class from not being initialized (if it is already initialized)
    • calladd_category_to_loadable_listLoading classification+loadMethods toloadable_categories

Now you can see the function call stack for the lazy and non-lazy classes

2.call_load_methods

Wake up +load method

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}
Copy the code
  • throughobjc_autoreleasePoolPushPush an automatic release pool
  • do-whileLoop begins
    • Loop through the class+loadMethod until you can’t find it
    • Call once in a class+loadmethods
  • Push an automatic release pool with objc_autoreleasePoolPop

5, initalize analysis

The Apple documentation on Initalize says so

Initializes the class before it receives its first message. Called before the class receives the first message. Discussion The runtime sends initialize to each classin a program exactly one time just before the class, or any class that inherits from it, is sent its first message from within the program. (Thus the method may never be invoked if the class is not used.) The runtime sends the initialize message to classes inA threadsafe spam. Superclasses receive this message before their subclasses. Runtime Send a threadsafe spam. Superclasses receive this message before their subclasses. Or any class it inherits from, it sends the first message in the program. (Therefore, the method may never be called when the class is not in use.) The runtime sends a thread-safe initialization message. The parent class must be called before the child class.Copy the code

Then we find lookUpImpOrForward in objC source code

lookUpImpOrForward->initializeAndLeaveLocked->initializeAndMaybeRelock->initializeNonMetaClass

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{...if(initialize && ! cls->isInitialized()) { cls = initializeAndLeaveLocked(cls, inst, runtimeLock); . }... }static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
    return initializeAndMaybeRelock(cls, obj, lock, true);
}

static Class initializeAndMaybeRelock(Class cls, id inst,
                                      mutex_t& lock, bool leaveLocked)
{... initializeNonMetaClass (nonmeta); ...}Copy the code

Call the parent class Initialize recursively in initializeNonMetaClass, and then call callInitialize

/*********************************************************************** * class_initialize. Send the '+initialize' message on demand to any * uninitialized class. Force initialization of superclasses first. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void initializeNonMetaClass(Class cls)
{... supercls = cls->superclass;if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    ...
    {
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]", pthread_self(), cls->nameForLogging()); }}... }Copy the code

CallInitialize is a normal message to send

void callInitialize(Class cls)
{((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}
Copy the code

Conclusions about Initalize:

  • initializeCalled before the first method of the class or its subclass is called (before the message is sent)
  • Add only in the classinitializeBut if you don’t use it, you won’t call itinitialize
  • Of the parent classinitializeMethods are executed before subclasses
  • When a subclass is not implementedinitializeMethod, the parent class is calledinitializeMethods; The subclass implementationinitializeMethod overrides the parent classinitializemethods
  • When more than one classification is implementedinitializeMethod, which overrides the methods in the class and executes only one (which executes the classifiers last loaded into memory)

Write in the back

From class structure, message sending, DYLD to the loading process of class and classification, the author has carried on a small wave of exploration of the process of loading -> use, next will start to share the dry matter – the bottom level of the questions