Objective-C:Category

Verbatim https://www.dazhuanlan.com/2019/08/25/5d623b90a7eb2/

 

Foreword

No matter how perfect the design of the class, in the evolution of demand in the future, are likely to encounter some unforeseen circumstances. How does that extend the existing class? In general, the combination of inheritance and is a good choice. But in Objective-C 2.0, a category that also provides a language feature, you can dynamically add new behaviors to existing classes. Today category has spread to every corner of the Objective-C code from Apple official framework to various open-source framework, the complex features a large APP to simple applications, catagory everywhere. This paper made a more comprehensive category finishing, I hope the readers benefit.

Brief introduction

This article is finishing when learning Objective-C runtime source code into the main analysis of the category in the realization of the principles and runtime layer and category-related aspects, including:

  • Entering the treasure category Introduction
  • Even analogy matter category and extension
  • Burning the midnight oil peruse true category
  • How to trace the origin category loading
  • Offshoot last years + load category and methods
  • And a method of covering analogy category
  • Category to the next level and associated objects

Entering the treasure Category Description

Category is added after 2.0 language features of Objective-C, the main role is to add a method to Category existing classes. In addition, apple also recommended Category of the other two usage scenarios, see the Apple Category document .

  • It can be achieved in several different classes of separate documents inside. This has several obvious benefits,
    • Single document can reduce the volume of b) can be organized into different functions in different category
    • A class can be done jointly by multiple developers
    • Category want to be loaded on demand and so on.
  • Declare private methods

But in addition to apple recommended usage scenario, the majority of developers Brain hole wide open, but also spawned a category of several other usage scenarios:

  • Simulate multiple inheritance
  • The framework of public private methods

What is the Objective-C language features may not for pure dynamic language, such as javascript, you can at any time for a "class" objects or add any methods and instance variables. But not so for "dynamic" language, this really is an amazing feature.

Even things Category analogy and Extension

extension looks like an anonymous category, but the extension name of the category and there are almost entirely two things. extension at compile time resolution, which is part of the class, @interface at compile documentation and head of documentation and the realization of @implement together to form a complete class, it is accompanied by class and produce, also will perish together. extension is generally used private information behind class, you must have a source to add extension class as a class, the class so you can not add to the system such as NSString extension. (See Apple documentation )

Burning the midnight oil peruse true category

We know that all of OC classes and objects in the runtime layer is represented by struct, category is no exception, the runtime layer, category structure for category_t (this definition can be found in objc-runtime-new.h in), it contains

  1. The class name (name)
    • Class (cls)
    • Examples of the method to a list of all the added class category in (instanceMethods)
    • category list of all the added class method (ClassMethods)
    • A list of all protocols category implementation (protocols)
    • All properties category added (instanceProperties)
typedef 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;
} category_t;

From the definition of the category can be seen as a category may (can add an instance method, the class method can be achieved even protocol, add properties) and not for the (instance variables can not be added). ok, we went to look at the category to write a category in the end why things:

MyClass.h:

#import <Foundation/Foundation.h>

@interface MyClass : NSObject

- (void)printName;

@end

@interface MyClass(MyAddition)

@property(nonatomic, copy) NSString *name;

- (void)printName;

@end

MyClass.m:

#import "MyClass.h"

@implementation MyClass

- (void)printName
{
    NSLog(@"%@",@"MyClass");
}

@end

@implementation MyClass(MyAddition)

- (void)printName
{
    NSLog(@"%@",@"MyAddition");
}

@end

We use the clang command category in the end to see what will become:

clang -rewrite-objc MyClass.m

Well, we got a 3M size, .cpp document 10w multi-line (which is definitely worth Tucao Apple's point), we ignore everything and we have nothing to do in the final document, we find the following code fragment:

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_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,

};

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_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
name
};

extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_MyClass;

static struct _category_t _OBJC_$_CATEGORY_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"MyClass",
0, // &OBJC_CLASS_$_MyClass,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition,
0,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MyClass_$_MyAddition,
};
static void OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition(void ) {
_OBJC_$_CATEGORY_MyClass_$_MyAddition.cls = &OBJC_CLASS_$_MyClass;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition,
};
static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= {
&OBJC_CLASS_$_MyClass,
};
static struct _class_t *_OBJC_LABEL_NONLAZY_CLASS_$[] = {
&OBJC_CLASS_$_MyClass,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_MyClass_$_MyAddition,
};

We can see that,

  1. First, the compiler generates a list of examples of methods OBJC $ _CATEGORY_INSTANCE_METHODSMyClass $ _MyAddition and property list OBJC $ _PROP_LISTMyClass $ _MyAddition, both names have followed a common prefix + class name + category naming names, but the list of instance methods inside it is filled MyAddition we wrote in this category which method printName, while the list of attributes inside filling is what we add in MyAddition in the name attribute. There is also a need to be aware of the fact that category name used to all kinds of lists, and category structure behind the name itself, but also to modify the static, so our category name can not be repeated in the same compilation unit, otherwise there will be Compile Error.
    • Secondly, the compiler generates the category itself OBJC $ _CATEGORYMyClass $ _MyAddition, and with a list of previously generated to initialize the category itself.
    • Finally, the compiler objc_catlist section was stored in the DATA section of a size of the array category_t 1 L_OBJC_LABELCATEGORY $ (Of course, if there are multiple category, corresponding to the length of the array generates ^ _ ^), for operating the category of load. Here, the compiler work on drawing to a close, for the category how to load at runtime, we announced the next section.

How to trace the origin category loading

We know that running Objective-C runtime is dependent on the OC, and OC of runtime libraries and other systems, is OS X and iOS by dyld dynamic loading. For more dyld students can be the venue here .

For OC operation, the inlet as follows (in objc-os.mm document):

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;

    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    lock_init();
    exception_init();

    // Register for unmap first, in case some +load unmaps something
    _dyld_register_func_for_remove_image(&unmap_image);
    dyld_register_image_state_change_handler(dyld_image_state_bound,
                                             1/*batch*/, &map_images);
    dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}

The above are attached to a class category is map_images occurs when, in new-ABI standard, _objc_init map_images call eventually calls inside objc-runtime-new.mm inside _read_images method, a method in _read_images end, the following code fragment:

// Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist =
            _getObjc2CategoryList(hi, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            class_t *cls = remapClass(cat->cls);

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = NULL;
                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 (isRealized(cls)) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s",
                                 getName(cls), cat->name,
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols 
                /* ||  cat->classProperties */)
            {
                addUnattachedCategoryForClass(cat, cls->isa, hi);
                if (isRealized(cls->isa)) {
                    remethodizeClass(cls->isa);
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)",
                                 getName(cls), cat->name);
                }
            }
        }
    }

First, we get catlist is mentioned in the previous section compiler category_t array prepared for us, on how to load catlist itself, if we do not watch, and this relationship category itself is not large, interested students Apple may be to study the following binary format and load mechanism.

Omitted PrintConnecting this thing for the log, the code is easy to understand:

  1. Examples of the method of the category, and protocol attributes added to the class
  2. Category class added to the methods and protocols class metaclass

It is noteworthy that, a short comments in the code / || cat->classProperties /, Apple seems to have had plans to add attributes to the class of ah. ok, we then looked inside, various lists category is how the final addition to the class, method Take for instance the list of examples: In the above code snippet in, addUnattachedCategoryForClass just do a class and category association mapping, and remethodizeClass is the real hero to deal with matters added.

static void remethodizeClass(class_t *cls)
{
    category_list *cats;
    BOOL isMeta;

    rwlock_assert_writing(&runtimeLock);

    isMeta = isMetaClass(cls);

    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls))) {
        chained_property_list *newproperties;
        const protocol_list_t **newprotos;

        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s",
                         getName(cls), isMeta ? "(meta)" : "");
        }

        // Update methods, properties, protocols

        BOOL vtableAffected = NO;
        attachCategoryMethods(cls, cats, &vtableAffected);

        newproperties = buildPropertyList(NULL, cats, isMeta);
        if (newproperties) {
            newproperties->next = cls->data()->properties;
            cls->data()->properties = newproperties;
        }

        newprotos = buildProtocolList(cats, NULL, cls->data()->protocols);
        if (cls->data()->protocols  &&  cls->data()->protocols != newprotos) {
            _free_internal(cls->data()->protocols);
        }
        cls->data()->protocols = newprotos;

        _free_internal(cats);

        // Update method caches and vtables
        flushCaches(cls);
        if (vtableAffected) flushVtables(cls);
    }
}

And for instance method to add class will attachCategoryMethods to call this method, we look to the attachCategoryMethods:

static void 
attachCategoryMethods(class_t *cls, category_list *cats,
                      BOOL *inoutVtablesAffected)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    BOOL isMeta = isMetaClass(cls);
    method_list_t **mlists = (method_list_t **)
        _malloc_internal(cats->count * sizeof(*mlists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int i = cats->count;
    BOOL fromBundle = NO;
    while (i--) {
        method_list_t *mlist = cat_method_list(cats->list[i].cat, isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= cats->list[i].fromBundle;
        }
    }

    attachMethodLists(cls, mlists, mcount, NO, fromBundle, inoutVtablesAffected);

    _free_internal(mlists);

}

attachCategoryMethods do the job is relatively simple, it's just the list of all the category of an instance method makes up a large list of instance methods, and then transferred to the attachMethodLists method (I swear this is the last piece of code in this section we look at the ^ _ ^), this method is a little long, we only look at short:

for (uint32_t m = 0;
             (scanForCustomRR || scanForCustomAWZ)  &&  m < mlist->count;
             m++)
        {
            SEL sel = method_list_nth(mlist, m)->name;
            if (scanForCustomRR  &&  isRRSelector(sel)) {
                cls->setHasCustomRR();
                scanForCustomRR = false;
            } else if (scanForCustomAWZ  &&  isAWZSelector(sel)) {
                cls->setHasCustomAWZ();
                scanForCustomAWZ = false;
            }
        }

        // Fill method list array
        newLists[newCount++] = mlist;
    .
    .
    .

    // Copy old methods to the method list array
    for (i = 0; i < oldCount; i++) {
        newLists[newCount++] = oldLists[i];
    }

There are two points to note:

  1. category approach is not "completely replaced" the original class has some methods, that is, if the original category and class has methodA, then after the completion of an additional category, class method there will be a list of two methodA
  2. category approach was brought to the front of the list of new methods, and methods of the original class was placed behind the new approach of the list, which is what we usually say the category approach will "cover" out of the original class method of the same name, which is because the find method is to find the time to run down the list of methods of sequence, as long as it is a method to find the corresponding name, will give up ^ _ ^, there may be behind the same method name everyone knows.

Offshoot last years + load category and methods

We know that in the class and category can have + load method, then there are two problems:

  1. When + load class method calls, we can call the method category declared it?
  2. So many months + load method calling sequence is Zeyang it?

In view of the above sections of the code that we look too, for these two questions let's look a little intuitive:

Our code, there are two category of MyClass and MyClass (Category1 and Category2), and two category MyClass are added + load method, and Category1 and Category2 have written MyClass of printName method. Click Edit Scheme in Xcode, add the following two environment variables (print log information to perform load method, and when the load category, more environment options can be found in objc-private.h):

Run the project, we will see the control panel to print a lot of things out, we only find the information we want, in the following order:

objc[1187]: REPLACED: -[MyClass printName] by category Category1
objc[1187]: REPLACED: -[MyClass printName] by category Category2
.
.
.
objc[1187]: LOAD: class 'MyClass' scheduled for +load
objc[1187]: LOAD: category 'MyClass(Category1)' scheduled for +load
objc[1187]: LOAD: category 'MyClass(Category2)' scheduled for +load
objc[1187]: LOAD: +[MyClass load]
.
.
.
objc[1187]: LOAD: +[MyClass(Category1) load]
.
.
.
objc[1187]: LOAD: +[MyClass(Category2) load]

Therefore, the above two issues, the answer is obvious:

  1. You can call, because the additional category to the category of work will be performed prior to + load methods
    • Execution order is + first load category, the category, and the category + load execution order is determined according to order of compilation. The current compilation order is as follows:

We adjust to compile a sequence of Category1 and Category2, run. ok, we can see the output sequence control panel has changed:

objc[1187]: REPLACED: -[MyClass printName] by category Category2
objc[1187]: REPLACED: -[MyClass printName] by category Category1
.
.
.
objc[1187]: LOAD: class 'MyClass' scheduled for +load
objc[1187]: LOAD: category 'MyClass(Category2)' scheduled for +load
objc[1187]: LOAD: category 'MyClass(Category1)' scheduled for +load
objc[1187]: LOAD: +[MyClass load]
.
.
.
objc[1187]: LOAD: +[MyClass(Category2) load]
.
.
.
objc[1187]: LOAD: +[MyClass(Category1) load]

Although the order of execution for + load is such that, but for the "cover" method out, you will first find a compilation of the last category in the corresponding method.

In this section we only use very intuitive way to get answers to your questions, interested students can continue to study the OC of runtime code.

And a method of covering analogy category

In view of the above sections are the principles we've talked about, this section only one question:

How to call a method in the original class to be covered out of the category?

For this problem, we already know the category is actually not entirely replace the original class method of the same name, just in front of the category list method only, so long as we follow the method list to find the last name of the corresponding method, you can call the original class method:

Class currentClass = [MyClass class];
MyClass *my = [[MyClass alloc] init];

if (currentClass) {
    unsigned int methodCount;
    Method *methodList = class_copyMethodList(currentClass, &methodCount);
    IMP lastImp = NULL;
    SEL lastSel = NULL;
    for (NSInteger i = 0; i < methodCount; i++) {
        Method method = methodList[i];
        NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method)) 
                                        encoding:NSUTF8StringEncoding];
        if ([@"printName" isEqualToString:methodName]) {
            lastImp = method_getImplementation(method);
            lastSel = method_getName(method);
        }
    }
    typedef void (*fn)(id,SEL);

    if (lastImp != NULL) {
        fn f = (fn)lastImp;
        f(my,lastSel);
    }
    free(methodList);
}

Category to the next level and associated objects

As seen above, we know that there is a category instance variables can not be added to category. But we often need to add value and associated objects in the category, this time can help to achieve the associated object.

MyClass+Category1.h:

#import "MyClass.h"

@interface MyClass (Category1)

@property(nonatomic,copy) NSString *name;

@end

MyClass+Category1.m:

#import "MyClass+Category1.h"
#import <objc/runtime.h>

@implementation MyClass (Category1)

+ (void)load
{
    NSLog(@"%@",@"load in Category1");
}

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self,
                             "name",
                             name,
                             OBJC_ASSOCIATION_COPY);
}

- (NSString*)name
{
    NSString *nameObject = objc_getAssociatedObject(self, "name");
    return nameObject;
}

@end

But what is also associated objects exist? How to store? How to deal with the associated object is destroyed when the object? We look through the source code at runtime, there is a method _object_set_associative_reference in objc-references.mm document:

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                _class_setInstancesHaveAssociatedObjects(_object_getClass(object));
            }
        } else {
            // setting the association to nil breaks the association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

We can see all of the associated objects by AssociationsManager management, and AssociationsManager defined as follows:

class AssociationsManager {
    static OSSpinLock _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
public:
    AssociationsManager()   { OSSpinLockLock(&_lock); }
    ~AssociationsManager()  { OSSpinLockUnlock(&_lock); }

    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

AssociationsManager which is composed of a static AssociationsHashMap to store all the associated objects. This is equivalent to all the objects associated objects are present a global map inside. And the key is a pointer to map the address of the object (pointer address any two different objects must be different), and this value is another map of a AssociationsHashMap, which holds the associated object kv pair. In the destruction of logical objects inside, see objc-runtime-new.mm:

void *objc_destructInstance(id obj) 
{
    if (obj) {
        Class isa_gen = _object_getClass(obj);
        class_t *isa = newcls(isa_gen);

        // Read all of the flags at once for performance.
        bool cxx = hasCxxStructors(isa);
        bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);

        if (!UseGC) objc_clear_deallocating(obj);
    }

    return obj;
}

Ah, destroy objects objc_destructInstance runtime function of which will determine the object has no associated objects, and if so, will call _object_remove_assocations do cleanup work associated objects.

postscript

As Mr. Hou Jie talking about - "in front of the source, the non-secret", although Apple's Cocoa Touch framework is not open source, but the runtime Objective-C and Core Foundation is completely open source (at http: //www.opensource .apple.com / tarballs / can be downloaded to all open source). This series will continue to update the runtime source of learning, students can get enough of their own above-mentioned website to download the source code to learn. T Bank simple, if wrong, correct me hope.

 

 

 

 

 

Guess you like

Origin www.cnblogs.com/petewell/p/11408232.html