[Objective-C] explore the essence of the bottom layer of category

Time:2019-11-4

No matter how perfect a class design is, there may be some unpredictable situations in the future requirements evolution. How to extend the existing classes? In general, inheritance and composition are good choices. However, in Objective-C 2.0, category is also provided, which can dynamically add new behaviors to existing classes. Today, category has been found in every corner of Objective-C code, from Apple’s official framework to various open-source frameworks, from large-scale apps with complex functions to simple applications, category is everywhere. This paper makes a comprehensive arrangement of category, hoping to benefit readers.

  The function of category feature in Objective-C is as follows:

(1) the implementation of the class can be distributed to multiple different files or frameworks (supplement new methods).

(2) you can create a forward reference to a private method.

(3) you can add informal agreements to objects.

  The limitations of category features in Objective-C are as follows:

(1) a class can only add new methods to the original class, and can only add but not delete or modify the original methods, and cannot add new attributes to the original class.

(2) the method added by a class to the original class is globally valid and has the highest priority. If it has the same name as the method of the original class, the original method will be overwritten unconditionally.

I. the underlying implementation of category

Objective-C implements the feature of dynamic language through runtime. All classes and objects are represented by structure in runtime. Category is represented by structure category in runtime. The following is the specific representation of structure category:

typedef struct category_t {
    Const char * name; // the name of the main class
    Classref_t CLS; // class
    Structure method list * instancemethods; // list of instance methods
    Struct method list * classmethods; // list of class methods
    Struct protocol? List? T * protocols; // list of all protocols
    Struct property list * instanceproperties; // all properties added
} category_t;

It can also be seen from the definition of category that category can be (instance method, class method, even protocol and attribute can be added) and cannot be (instance variable cannot be added).

We will explore the implementation principle of category based on the source code of runtime. Open the runtime source project, in the file (objc-runtime-new.mmThe following functions are found in:

void _read_images(header_info **hList, uint32_t hCount)
{
    ...
        _free_internal(resolvedFutureClasses);
    }

    // Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist =
            _getObjc2CategoryList(hi, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            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;
            if (cat->instanceMethods ||  cat->protocols
                ||  cat->instanceProperties)
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                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" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols
                /* ||  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);
                }
            }
        }
    }

    // Category discovery MUST BE LAST to avoid potential races 
    // when other threads call the new category code before 
    // this thread finishes its fixups.

    // +load handled by prepare_load_methods()

    ...
}

We can see that category is processed as follows in this function:

(1) register the category and its main class (or metaclass) in the hash table;

(2) if the main class (or metaclass) has been implemented, rebuild its method list;

  Implementation principle of category:

  • At compile time, the methods implemented in the classification are generated into a structure method_list_t, generate a struct property list t from the declared properties, and then generate a struct category t from these structs.
  • Then save the structure category
  • At runtime, the runtime will get the structure category that we saved at compile time
  • Then add the instance method list, protocol list and attribute list in the structure category to the main class
  • Add the class method list and protocol list in the structure category? T to the main class’s metaclass

Two.Why do methods in category take precedence over methods in the original class?

The method list in category “is inserted in front of the method list of the main class (similar to using the next pointer in the linked list to insert), so the method implemented in category here will not really cover the method in the main class, but just insert the method of category in front of the method list. The runtime searches methods in the order of the method list. As soon as it finds the method with the corresponding name, it will stop searching. Here, there will be the illusion of overriding the method.
//It's probably inserted like this
newproperties->next = cls->data()->properties;
cls->data()->properties = newproperties;,

3. Why can’t instance variables be added to category?

We can know from the structure category that we can add instance method, class method, protocol and attribute in category. There is no objc ﹣ Ivar ﹣ list structure here, which means that we can’t add instance variables to the classification.

Because in the runtime, the memory layout of the object has been determined. Adding instance variables will destroy the internal layout of the class. This is the root cause that instance variables cannot be added in category.
 
Reference: in depth understanding of Objective-C: Category