Classification is often used in daily development, and the understanding of classification is limited to use. But its underlying implementation is not so well understood, this article mainly according to the source code exploration classification.

Introduction to classification

Category is a language feature added after Objective-C 2.0. Categories and categories actually refer to categories. The main purpose of a Category is to add methods to an existing class. Categories in Objective-C are a concrete implementation of the decorator pattern. Its main purpose is to dynamically add methods to a class without changing the original class.

Use of classification

  • Declare private methods
  • Decompose large class files
  • Expose Framework private methods
  • Simulate multiple inheritance (protocol is another option)

Characteristics of classification

  • 1. Classes are used to add methods to existing classes because there is no property list in the structure pointer of a class, only a list of methods.In principle, it can only add methods, not attributes (member variables), and you can actually add attributes in other ways ;
  • 2. @property can be written to a class, but will not be generatedsetter/getterMethods, which do not generate implementation or private member variables, will compile, but reference variables will report errors;
  • 3. If there is a method with the same name as the original class in the classification, the method in the classification will be called first, that is, the method of the original class will be ignored, and the call priority of the method with the same name isCategory > This class > Parent class;
  • 4. If more than one class has a method with the same name as the original class, the compiler decides who executes when calling the method. The compiler executes the method in the last class to participate in the compilation.
  • 5. Runtime resolution
  • 6. The namesake classification method takes effect depending on the compilation order
  • 7. Classification with the same name will cause compilation errors

Nature of classification

Compiling the.m file with clang yields the code shown below, which yields the contents of the.cpp.

clang -rewrite-objc main.m -o main.cpp
Copy the code

The compiled.cpP portion is shown below. Note A category is a _category_T structure.

static struct /_method_list_t/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_good __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"studyMusic", "v16@0:8", (void *)_I_NSObject_good_studyMusic}}
};

static struct /_method_list_t/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_good __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"studyEnglish", "v16@0:8", (void*)_C_NSObject_good_studyEnglish}}
 };
    
static struct /_prop_list_t/ {
     unsigned int entsize;  // sizeof(struct _prop_t)
     unsigned int count_of_properties;
     struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_NSObject_$_good **__attribute__** ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"goodSize","T@\"NSString\",&,N"}}
};

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject;

static struct _category_t _OBJC_$_CATEGORY_NSObject_$_good __attribute__*((used, section ("__DATA,__objc_const"))) = 
{
"NSObject",
0, // &OBJC_CLASS_$_NSObject,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_good,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_good,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NSObject_$_good,
};
Copy the code

View the categories in objC source code:

struct category_t { const char *name; // name classref_t CLS; WrappedPtr<method_list_t, PtrauthStrip> instanceMethods; WrappedPtr<method_list_t, PtrauthStrip> classMethods; Struct protocol_t *protocols; Struct property_list_t *instanceProperties; // Fields below this point are not always present on disk. struct property_list_t *_classProperties; // Class attribute method_list_t *methodsForMeta(bool isMeta) {if (isMeta) return classMethods; else return instanceMethods; } property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi); protocol_list_t *protocolsForMeta(bool isMeta) { if (isMeta) return nullptr; else return protocols; }};Copy the code

By compiling the middle layer code. CPP file compared to the objC4 source code to the conclusion:

  1. nameRepresents theclassificationName.
  2. Classification does not distinguish classification methods from instance methodsClass methods are inserted into classes, and class methods are inserted into metaclasses, so they don’t have their own metaclasses.
  3. Attribute union of classificationGetters and setters are not automatically generatedThis can be seen in the.cpp file

The loading process of classification

You can apply the “load_categories_nolock” method

According to the annotation, it can be seen that the loading of classification is mainly realized by attachCategories and attachToClass

static void load_categories_nolock(header_info *hi) { { bool hasClassProperties = hi->info()->hasCategoryClassProperties(); size_t count; auto processCatlist = [&](category_t * const *catlist) { for (unsigned i = 0; i < count; i++) { category_t *cat = catlist[i]; Class cls = remapClass(cat->cls); locstamped_category_t lc{cat, hi}; if (! cls) { continue; } // Process this category.if (CLS ->isStubClass()) {// Classes do not know their metaclasses before initialization, so we must add classes with class methods or attributes to the class itself. // methodizeClass () will find them and add them to the metaclass as needed. if (cat->instanceMethods || cat->protocols || cat->instanceProperties || cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties)) { objc::unattachedCategories.addForClass(lc, cls); }} else {// Register the classification to the class information. // If the class is implemented, The reconstruction method of the class list if (cat - > instanceMethods | | cat - > separate protocols | | cat - > instanceProperties) {if (CLS - > isRealized ()) { attachCategories(cls, &lc, 1, ATTACH_EXISTING); } else { objc::unattachedCategories.addForClass(lc, cls); } } if (cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties)) { if (cls->ISA()->isRealized()) { attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS); } else { objc::unattachedCategories.addForClass(lc, cls->ISA()); }}}}}; processCatlist(hi->catlist(&count)); processCatlist(hi->catlist2(&count)); }Copy the code

attachCategories

Append a list of methods, attributes, and protocols from the category to the class. Categories in Cats are assumed to be loaded and sorted in the order they are loaded, with the oldest category coming first.

static void attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count, int flags) { if (slowpath(PrintReplacedMethods)) { printReplacements(cls, cats_list, cats_count); } if (slowpath(PrintConnecting)) { _objc_inform("CLASS: attaching %d categories to%s class '%s'%s", cats_count, (flags & ATTACH_EXISTING) ? " existing" : "", cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : ""); } /** * Only a few classes had more than 64 categories at release. * This uses a little stack and avoids malloc. * Categories must be added in the correct order, from back to front. To do this by chunking, we iterate over cats_list * from front to back, building the local buffer backwards, and calling attachLists on the block. AttachLists put the list first, so the final results are in the expected order. */ constexpr uint32_t ATTACH_BUFSIZ = 64; method_list_t *mlists[ATTACH_BUFSIZ]; property_list_t *proplists[ATTACH_BUFSIZ]; protocol_list_t *protolists[ATTACH_BUFSIZ]; Uint32_t McOunt = 0; uint32_t propcount = 0; uint32_t protocount = 0; bool fromBundle = NO; bool isMeta = (flags & ATTACH_METACLASS); auto rwe = cls->data()->extAllocIfNeeded(); const char *mangledName = cls->nonlazyMangledName(); if (strcmp(mangledName, "FFPerson") == 0) { if (! isMeta) { printf("%s -FFPerson.... \n",__func__); } } for (uint32_t i = 0; i < cats_count; i++) { auto& entry = cats_list[i]; Method_list_t *mlist = entry.cat->methodsForMeta(isMeta); If (mlist) {if (McOunt == ATTACH_BUFSIZ) {prepareMethodLists(CLS, mlists, McOunt, NO, fromBundle, __func__); // Back insert rwe->methods.attachLists(mlists, McOunt); mcount = 0; } mlists[ATTACH_BUFSIZ - ++mcount] = mlist; fromBundle |= entry.hi->isBundle(); } // Insert property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi); if (proplist) { if (propcount == ATTACH_BUFSIZ) { rwe->properties.attachLists(proplists, propcount); propcount = 0; } proplists[ATTACH_BUFSIZ - ++propcount] = proplist; } // Insert protocol_list_t *protolist = entry. Cat ->protocolsForMeta(isMeta); if (protolist) { if (protocount == ATTACH_BUFSIZ) { rwe->protocols.attachLists(protolists, protocount); protocount = 0; } protolists[ATTACH_BUFSIZ - ++protocount] = protolist; } } if (mcount > 0) { prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle, __func__); rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount); if (flags & ATTACH_EXISTING) { flushCaches(cls, __func__, [](Class c){ // constant caches have been dealt with in prepareMethodLists // if the class still is constant here, it's fine to keep return ! c->cache.isConstantOptimizedCache(); }); } } rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount); rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount); }Copy the code

Q&A

Q: ClassifiedObject methods.Class methodWhere do they live?

The object methods of all classes of a class are stored in class objects, and the class methods of all classes are stored in metaclasses

Q: How are classified methods added to the list of class object methods?

About the process

  • 1. Load all Category data of a class through the Runtime

  • 2. Merge all Category methods, attributes, and protocol data into a large array

  • 3. The Category data that will be compiled later will come before the array

  • 4. Insert the merged classification data (methods, attributes, and protocols) before the original data of the class

Q: Implementation principle of Category

  • After Category compilation, the underlying structure is struct category_t, which stores classified object methods, class methods, attributes, and protocol information
  • At runtime, the Runtime merges the Category data into the class information (class object, metaclass object).

Q: What is the difference between a Category and a Class Extension?

  • When Class Extension is compiled, its data is already included in the Class information
  • Category data is merged into the class information at runtime

Q: +load method call order?

1. Call the class’s +load method first
  • 1.1 Call In Compile Order (compile first, call first)
  • 1.2 Call +load of the parent class first and then call +load of the child class
2. Call the +load method of the classification
  • 2.1 Invocation in Compile Order (Compile first, invoke first)
// 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();
Copy the code
  • The +load of each class and category is called only once during program execution and only once when the class is loaded
  • There is no category +load method to override the class +load method

Q: What is the call order of load and initialize?

1.load
  • B) The parent class load is called first before the subclass load is called
  • A) If the class is compiled first, load will be called first
2.initialize

1> Initialize the parent class. 2> Initialize the child class.