Looking into the Underlying Implementation of iOS — The Nature of OC Objects (1)

The Essence of OC Object (2)

Snoop on the underlying implementation of iOS –OC object classification: instance, class, META-calss object ISA and superclass

A peek into the nature of the underlying iOS implementation — KVO/KVC

Summary of the basic principles of iOS – Using the Runtime source code analysis Category of the underlying implementation…

Preface:

This article summarizes the internal implementation part of the Category. There are many code parts and annotations, which may be boring to read. But please stick to it. There will be more deliveries.

This article covers the internal structure of Class objects. Please refer to the basic implementation of iOS –OC object classification: instance, Class, META-calss object isa and superclass

Think about:

  1. How does Category work?
  2. Why are methods in a Category called first?
  3. Extension question – If the same method is implemented in multiple classes, which method is called first when it is called?
  4. What’s the difference between extension and classification?

Basic Category implementation

Let’s first look at the classification code and the implementation of the code can optionally be skipped

///> main.h
int main(int argc, const char *argv[]){
	@autoreleasepool{
	  Person *person = [[Person alloc] init]
  	[person run];
	  [person test];
  	[person eat];
	}
	return 0
}

///> person
@interface Person: NSObject
@end
@implementation Person
- (void)run{
  Nslog(@"run")
}
@end

///> person+test
@interface Person(test)
- (void)test;
@end
@implementation Person(test)
- (void)test{
  Nslog(@"test")
}
@end
  
///> person+Eat
@interface Person(eat)
- (void)eat;
@end
@implementation Person(eat)
- (void)eat{
  Nslog(@"eat")
}
@end
Copy the code

After the underlying structure of the classification is compiled

When compiled, the first time the program runs, all of the classification methods are initially stored in the structure (each class has a new structure object),

After compilation, the category is stored in the category_t structure instead of being merged into the original class. Each category generates a catrgory_t structure, and the methods, protocols, properties, and so on from the category are merged into the original class at runtime.

The following is the process of viewing the source code, annotated at each step, which is a bit boring, but will be very beneficial after reading it.

Classification code C++ source code analysis

To use:

Xcrun -SDK iphoneOS clang-arch arm64 -oc Source file -o Output CPP fileCopy the code

Command to view conversion to C\C++ code. There will be a file that generates xxx.cpp which is exactly what we want

You can drag it into Xcode for easy searching

Next, a direct search for category_t yields the following structure, which I’ve put comments behind

struct _category_t{ const char *name; Struct _class_t * CLS; ///> class const struct _method_list_t *instance_methods; Const struct _method_list_t *class_methods; ///> const struct _protocol_t *protocols; Const struct _prop_list_t *properties; / / / > attribute}Copy the code

Each time a class is created, a category_t structure is created at the root as follows

static struct _category_t OBJC_$CATEGORY_PERSON_The $_Test __attribute__ ((userd.section(" __DATA, __objc_const"))) = {
  ///> Belongs to the classification of that class
  "Person".///> class
  0.///> List of object methods
  (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$Test,
  ///> List of class methods
  (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$Test,
  ///> Protocol list
  0.// (const _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_PERSON_$_Test,	
  ///> Property list
  0.// (const _prop_list_t *)&_OBJC_$_PROP_LIST_PERSON_$_Test,
}
Copy the code

Next, take a look at how the Runtime source code merges the categories,

Runtime source code analysis

Start by downloading the source code for Runtimed. —— open here with Xcode

  1. A search for “catrgory_t {“

    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

    You can see that the structure in the Runtime is similar to that of category_T above.

  2. The Runtime program entry file is objc-os.mm,

  3. I’m going to go straight to the Category code and search for Discover Categories in objc-Runtime-new. mm. Comment code for

      // Discover categories. 
       for (EACH_HEADER) {
            /** catList is a two-dimensional array. Each category creates a category_t structure. This two-dimensional array contains the contents of two categories, such as eat and test
            category_t **catlist = 
                _getObjc2CategoryList(hi, &count);
            bool hasClassProperties = hi->info()->hasCategoryClassProperties();
    
            ///> iterate over the contents of each array
            for (i = 0; i < count; i++) {
                ///> Get a separate CATEGORY_t structure
                category_t *cat = catlist[i];
                ///> Remap class to fetch the class of the structure
                Class cls = remapClass(cat->cls);
    
                if(! cls) {// Category's target class is missing (probably weak-linked).
                    // Disavow any knowledge of this category.
                    catlist[i] = nil;
                    if (PrintConnecting) {
                        _objc_inform("CLASS: IGNORING category \? \? \? (%s) %p with "
                                     "missing weak-linked target class", 
                                     cat->name, cat);
                    }
                    continue;
                }
    
                // Process this category. 
                // First, register the category with its target class. 
                // Then, rebuild the class's method lists (etc) if 
                // the class is realized. 
                bool classExists = NO;
                // determine the contents of the structure
                if (cat->instanceMethods ||  cat->protocols  
                    ||  cat->instanceProperties) 
                {
                    addUnattachedCategoryForClass(cat, cls, hi);
                    if (cls->isRealized()) {
                        /// core content: reorganize methods in a class
                        remethodizeClass(cls);
                        classExists = YES;
                    }
                    if (PrintConnecting) {
                        _objc_inform("CLASS: found category -%s(%s) %s", 
                                     cls->nameForLogging(), cat->name, 
                                     classExists ? "on existing class" : ""); }}if (cat->classMethods  ||  cat->protocols  
                    ||  (hasClassProperties && cat->_classProperties)) 
                {
                    addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                    if (cls->ISA()->isRealized()) {
                        /// reorganize the metaclass methods in the class
                        remethodizeClass(cls->ISA());
                    }
                    if (PrintConnecting) {
                        _objc_inform("CLASS: found category +%s(%s)", cls->nameForLogging(), cat->name); }}}}Copy the code

    The core method is found in the above code: remethodizeClass is used twice, reorganizing the method of the class and the method of the metaclass

  4. Command + Single machine, enter

    static void remethodizeClass(Class cls)
    {
        category_list *cats;
        bool isMeta;
        runtimeLock.assertWriting();
        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)" : "");
            }
            
         		///> Attach the class to the code call, passed the class object, classification.
    	      ///> cls: [Person class]
    				///> cats: [category_t(test), category_t(eat)]
            attachCategories(cls, cats, true /*flush caches*/);        
            free(cats); }}Copy the code
  5. Command enters attachCategories(CLS, cats, true /flush caches/); methods

    ///> cls: [Person class]
    ///> cats: [category_t(test), category_t(eat)]
    static void 
    attachCategories(Class cls, category_list *cats, bool flush_caches){
        if(! cats)return;
        if (PrintReplacedMethods) printReplacements(cls, cats);
    
        ///> Is a metaclass object
        bool isMeta = cls->isMetaClass();
    
        // fixme rearrange to remove these intermediate allocations
        ///> malloc allocates memory
        / / / > method arrays two-dimensional eg: [[method_t method_t], [method_t, method_t]]
        method_list_t **mlists = (method_list_t* *)malloc(cats->count * sizeof(*mlists));
          
        ///> Property array eg:[[property_t,property_t], [property_t,property_t]]
        property_list_t **proplists = (property_list_t* *)malloc(cats->count * sizeof(*proplists));
      
        Eg :[[protocol_t,protocol_t], [protocol_t,protocol_t]]
        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--) {
            ///> Fetch a category
            auto& entry = cats->list[i];
            ///> Retrieve the object method in the classification
            method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
            ///> Place the list of methods for each category in the two-dimensional array defined above!
            if (mlist) {
                mlists[mcount++] = mlist;
                fromBundle |= entry.hi->isBundle();
            }
    
            property_list_t *proplist = 
                entry.cat->propertiesForMeta(isMeta, entry.hi);
            ///> place each classified protocol list array in the two-dimensional array defined above!
            if (proplist) {
                proplists[propcount++] = proplist;
            }
    
            protocol_list_t *protolist = entry.cat->protocols;
            ///> Place the attribute list array of each category in the two-dimensional array defined above!
            if(protolist) { protolists[protocount++] = protolist; }}///> Fetch the data from the class object
        auto rw = cls->data();
    
        prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
        /** core code: rW: class object structure has an ERW structure, this step is to merge the data into the rW structure of the class object, please refer to the article: attach all the classified object methods to the class object. When running D, merge the classified data into the original class object. * /
        rw->methods.attachLists(mlists, mcount);
        
    		free(mlists);
        if (flush_caches  &&  mcount > 0) flushCaches(cls);
    
        ///> List of attribute methods
        rw->properties.attachLists(proplists, propcount);
        free(proplists);
        
        ///> List of the same protocol methods
        rw->protocols.attachLists(protolists, protocount);
        free(protolists);
    }
    Copy the code
  6. Rw ->methods.attachLists(mlists, McOunt); In the method

        /** addedLists: [[method_t, method_t],[method_t, method_t]] addedCount: 2 s number of two-dimensional arrays */
        void attachLists(List* const * addedLists, uint32_t addedCount) {
            if (addedCount == 0) return;
    
            if (hasArray()) {
                // many lists -> many lists
                ///> The size in the original array is each added to this category
                uint32_t oldCount = array()->count;
                ///> New array size: original plus newly passed total size
                uint32_t newCount = oldCount + addedCount;
                ///> Realloc reallocates memory newCont
                ///> Expand the size of the array to merge the categories
                ///> Memory needs to be reallocated
                setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
                array()->count = newCount;
                
                /* Memory move array()->lists original method list addedCount (array()-> Lists + addedCount addedCount is moved to the end of the method! * /
                memmove(array()->lists + addedCount, array()->lists, 
                        oldCount * sizeof(array()->lists[0]));
                
                /* memory move copy array()->lists original method list addedLists into the classification list above the method list has been expanded and the original class with the method list moved back to the number of addedCount is Copy the list of methods for the incoming classification to the top of array()-> Lists (original method list), so this is why the classified data is called first */
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));
            }
            else if (!list  &&  addedCount == 1) {
                // 0 lists -> 1 list
                list = addedLists[0];
            } 
            else {
                // 1 list -> many lists
                List* oldList = list;
                uint32_t oldCount = oldList ? 1 : 0;
                uint32_t newCount = oldCount + addedCount;
                setArray((array_t *)malloc(array_t::byteSize(newCount)));
                array()->count = newCount;
                if (oldList) array()->lists[addedCount] = oldList;
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0])); }}Copy the code

Summarize some problems of classification

From source code analysis we can see that,

  1. How does Category work?

    Categoty_t: Categoty_t: categoty_t: cateGoty_t

    1. Compilation stage:

      Category_t structure is generated for each category, in which categoryname, class, object method list, class method list, protocol list, and attribute list are stored.

    2. Runtime Runtime phase:

      Will generate the classification of the data merging into the original class, a class classification data will be incorporated into a large array of (after participation in the classification of the compilation will be in front of the array), the method of classification list, property list, the protocol list, etc in the two-dimensional array, and then to organize the class methods, categorizing each corresponding list of merged into the original class list. (The list of original classes is expanded according to the number of two-dimensional arrays before merging, and then the list of categories is placed first)

  2. Why are methods in a Category called first?

    As mentioned above, expanding the array moves the list of methods in the original class to the back and puts the classified list data in front, so the classified data is called first

  3. Extension question – If the same method is implemented in multiple classes, which method is called first when it is called?

    When you have the same methods in multiple classes, the classification list is added according to the order in which the post-compiled classes are compiled first, so look at the order in Build Phases –> compile Sources. The last to participate in the compilation comes first.

  4. The difference between extension and classification

    The @interface extension is an anonymous category, not a category. Property additions are added to the class at compile time

    Categories were merged in runtime.


Reference:

  1. Find objC4, download version number maximum is the latest Source code
  2. MJ teacher bottom related video

Thanks again!!


If there is a mistake, please point out!!