Abstract

The main purpose of a Category is to add methods, attributes, and protocols to existing classes.

On the one hand, category_T and related structures are generated at compile time.

At run time, on the other hand, these methods, properties, and protocols are added to the class.

Category_t structure

/ / objc4-750.1 / runtime/objc runtime - new. H
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; / / 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);
};
Copy the code

In particular, it does not inherit from Objc_Object.

Also, there are no Ivars, which explains why you can’t add variables to a Category.

As an aside, _classProperties is rare, but it turns out that Xcode8 is a new feature for Swift.

The syntax is as follows:

@property (class) NSString *someString;
Copy the code

Compile time

  1. At compile time, different categories are compiled into different category_t structures.
  2. Lists of methods, attributes, protocols, and so on in a Category are compiled into different structures and stored in category_t.
  3. The Category information will eventually be logged in Mach-O.

In particular, let’s do a simple code and see what it compiles.

@implementation Cat (JD)
- (void)run {
    NSLog(@"run");
}
@end
Copy the code

To c++ code

Clang-x objective-c-rewrite-objc-fobjc-arc-fobjc-Runtime =ios-8.0.0 Cat+ jd.mCopy the code

You can see that the run method is converted to the following static function

// @implementation Cat (JD)

static void _I_Cat_JD_run(Cat * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_mv_t42fx9pj0mn9k96m8y1rwwd40000gn_T_Cat_JD_0bdbd6_mi_0);
}
// @end
Copy the code

A _method_LIST_t named _OBJC_$_CATEGORY_INSTANCE_METHODS_Cat_$_JD is created.

And associated with the static function above.

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_Cat_$_JD __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"run"."v16@0:8", (void *)_I_Cat_JD_run}}
};
Copy the code

Create a _category_T (category_T) structure named _OBJC_$_CATEGORY_Cat_$_JD.

static struct _category_t _OBJC_The $_CATEGORY_Cat_The $_JD __attribute__ ((used.section(" __DATA, __objc_const"))) =
{
	"Cat".0.// &OBJC_CLASS_$_Cat,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Cat_$_JD,
	0.0.0};Copy the code

Associated with the original class

static void OBJC_CATEGORY_SETUP_$_Cat_$_JD(void) {
	_OBJC_$_CATEGORY_Cat_$_JD.cls = &OBJC_CLASS_$_Cat;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
	(void *)&OBJC_CATEGORY_SETUP_$_Cat_$_JD,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$[1] __attribute__((used.section(" __DATA, __objc_catlist.regular.no_dead_strip"))) = {
	&_OBJC_$_CATEGORY_Cat_$_JD,
};
Copy the code

The source code contains sections (“__DATA,__objc_const”), which are the relevant storage locations for Mach-O and are used when loading.

Run time

When Objc initializes, get the category_T array generated at compile time from Mach-o.

The following operations are done:

  1. Add instance attributes, instance methods, and protocols from Category_T to class.
  2. Add class attributes, class methods, and protocols to metaClass.

Once added, the Category method, attribute, and protocol will all come first in the original list.

Let’s look at the implementation.

_read_images()

Starting with the ObjC initialization method _objc_init(), _read_images() is called.

/*********************************************************************** * _objc_init * Bootstrap initialization. Registers our image notifier with dyld. * Called by libSystem BEFORE library initialization time * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void _objc_init(void) {...// _objc_init->map_images->map_images_nolock->_read_images->realizeClass
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
Copy the code

In _read_images(), you add both instance methods, protocols, and instance attributes to the class and class methods, protocols, and class attributes to the metaClass.

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {
    ...
    // Discover categories.
    for (EACH_HEADER) {
        / / to get
        category_t **catlist =
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info() - >hasCategoryClassProperties(a);for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls); .// 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;

            // Instance methods, protocols, instance properties -> class
            if (cat->instanceMethods ||  cat->protocols
                ||  cat->instanceProperties)
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls); classExists = YES; }... }// Class methods, protocols, class attributes -> class
            if (cat->classMethods  ||  cat->protocols
                ||  (hasClassProperties && cat->_classProperties))
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA() - >isRealized()) {
                    remethodizeClass(cls->ISA()); }... }}}... }Copy the code

attachCategories()

The added logic is not in remethodizeClass(), but in attachCategories().

Its call stack

AttachCategories () core code is shown below.

As you can see, the category information that is loaded later is added first.

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{...// Add in reverse order
    // 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(a); }// Omit attribute, protocol...
    }

    auto rw = cls->data(a); .// call remethodizeClass(), need to update the cache; MethodizeClass () does not
    if (flush_caches  &&  mcount> 0) flushCaches(cls);

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

    // omit protocol...
}
Copy the code

list_array_tt::attachLists()

The logic for adding a list is list_array_tt::attachLists()

As you can see, the original list of classes is kept, but moved back in order, so when called, the classification method is called first.

/ / objc4-750.1 / runtime/objc runtime - new. H

void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;

    if (hasArray()) {
        // many lists -> many lists
        uint32_t oldCount = array()->count;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)realloc(array(), array_t: :byteSize(newCount)));
        array()->count = newCount;
        memmove(array()->lists + addedCount, array()->lists,
                oldCount * sizeof(array()->lists[0]));
        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

Many Lists -> many lists, key 2 lines of code

memmove(array()->lists + addedCount, array()->lists,
                oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
               addedCount * sizeof(array()->lists[0]));
Copy the code

So what you’re doing is you’re taking the list in the Category, moving it to the front of the original list, translating it into a graph.

summary

======== Compile time ========

Different categories are compiled into different category_t structures.

The list of methods, properties, protocols, and so on is also compiled into a structure stored in category_t.

All of this information will eventually be recorded in Mach-O.

======== Run time ========

When Objc initializes, get the category_T array generated at compile time from Mach-o.

The following operations are done:

  1. Add instance attributes, instance methods, and protocols from Category_T to class.
  2. Add class attributes, class methods, and protocols to metaClass.

When added, the Category method, properties, and protocol list come first.

doubt

As mentioned above, variable cannot be added to Category because category_t has no IVars.

Why not add ivars to category_T as well?

My personal views are as follows:

Given the structure of objC_class, the list of variables exists in class_ro_t and is immutable.

Whereas variables actually exist in the memory layout of objects, categories add things to the class and should not affect the object.

struct class_ro_t {
     // List of variables
     const ivar_list_t * ivars;
 }
Copy the code

But I do not know whether it is correct or not, the students who know the trouble to inform, grateful!

Thank you

Understand Objective-C: categories in depth

Apple Developer Category