On Category

A few days ago looking at Objective-C Category related content, mainly to understand how its underlying implementation under, said the popular point is how the content category defined bound to the class, the associated object is what there is of it? How to store? How to deal with the associated object is destroyed when the object?

Much of this paper reference depth understanding-C in Objective: the Category , no less detailed article, the text can be considered a summary version, make a note of their own, students can temper more anxious to see me this.

What Category is?

language feature category is added after 2.0 Objective-C, the main role is to add a method to category existing classes. In addition, apple also recommended two other category of use scenario 1

  • It can be achieved in several different classes of separate files inside. This has several obvious advantages, a) can reduce the volume of a single file b) can be organized into different functions in different category c) a class can be accomplished by a plurality of developers co-d) can be loaded on demand wants the category, and so on.
  • Declare private methods

Category analysis

Analysis of a certain kind of thing, so essential step is to know his definition, and then go about his real
now. So what is his definition of it. We went to write a category

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

After clangAfter compiling, we see a c ++ file, which we intercept the most important part of the code to explain

struct _category_t {
    const char *name; //类的名字
    struct _class_t *cls; //类
    const struct _method_list_t *instance_methods; // 实例方法列表
    const struct _method_list_t *class_methods; //类方法列表
    const struct _protocol_list_t *protocols; //协议列表
    const struct _prop_list_t *properties; //属性列表
};
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,
    {{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_MyClass_MyAddition_printName}}
};

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","T@\"NSString\",C,N"}}
};

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 _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_MyClass_$_MyAddition,
};
我们可以看到编译阶段,编译器帮忙生成了方法列表_OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition 和属性列表_OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition, 从上面解析出来的命名规则和static修饰,我们也可以推测出为何分类名要不一致,不然会报错的原因。最后一点是L_OBJC_LABEL_CATEGORY_$,我们看到其是一个数组,存储在Data段下的__objc_catlist,regular section中,某个类有几个分类,那么该数组的长度就是几

Knowing category structure, we look at it is how binding
we can see the following code snippet objc-runtime-new.mm file

// Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        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  
                ||  (hasClassProperties && 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);
                }
            }
        }
    }

Catlist which is generated by the compiler classification list, we see that by addUnattachedCategoryForClassand remethodizeClassbinding to achieve the classification to class. The above code, we also see the example of the method category, the protocols and attributes are added to the class, the class methods and protocols category is to add a metaclass to the class, since the block does not belong on this section range, not started. Back to the topic, addUnattachedCategoryForClassin fact, just do a classification and associated class, then we put their emphasis remethodizeClasson method

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();

    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)" : "");
        }
        
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

See remethodizeClasslater, we see once again that in turn calls attachCategoriesthe function

// 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)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    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--) {
        auto& entry = cats->list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

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

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

The comments by our general understanding of the function of doing things function, method of classification, properties, the protocol list is bound to the class. We have to look at the list of methods to bind an example of how to achieve other similar; instance method list of all category makes up a large list of instance method, call the attachListsfunction, we look at the function, it really is last call ...

/// 方法列表数组长度增加,newCount = oldCount + addedCount,新加的方法列表添加到原有的方法列表中
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]));
        }
    }

其中memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
这两句其实等价于如下语句
    for (i = 0; i < oldCount; i++) {
        newLists[newCount++] = oldLists[i];
    }

void *memmove( void* dest, const void* src, size_t count );由src所指内存区域复制count个字节到dest所指内存区域。
void *memcpy(void *dest, const void *src, size_t n);从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中

So far, the whole process has completed, which we found several things. Focus on the draw

  • We believe that the normal classification method is a method of covering the original class, in fact, this is not quite correct, precise, and if the classification class has methodA, in fact, the real list of methods existence of two methodA. Well above the source elaborated on this view
    * methods on the front of the class method category, but also because after a runtime mechanism to find ways to return directly, so with this method illusion class is covered

If there are multiple classification, then the final execution of the method which classify it? We know from the analysis of the code is the last to be compiled classification that is the latest to be called. By experiments we see is determined based on compilation order. In fact, we can use an environment variable OBJC_PRINT_REPLACED_METHODSto verify this view, the role of the environment variable islog methods replaced by category implementations

671666-cbd4eea6cb42f423.png
OBJC_PRINT_REPLACED_METHODS

log output:

objc[31548]: REPLACED: +[Student printName]  by category LKAdd1
objc[31548]: REPLACED: +[Student printName]  by category LKAdd2  

In fact, there are many values of environment variables, each value has a different purpose, such as we used to DYLD_PRINT_STATISTICSis the length of time before the main output to various parts of the occupied. About runtime environment variables can read my other article Environment the Variables , here will not start ...

The associated object

In summary, we know that is not classified add an instance variable, but many scenes, we may need to use. Mr. Key then leads presented later associated objects. Followed by a question: Where there is an associated object? How to store? How to deal with the destruction of the object when the associated object?

@interface MyClass(MyAddition)

@property(nonatomic, copy) NSString *name;

@end
@implementation MyClass(MyAddition)
- (NSString *)name {
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

By querying the source in runtime.h,runtime.mmto find the definition and implementation file

OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}

Follow it, we find a specific function in the file objc-references.mm

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);
                object->setHasAssociatedObjects();
            }
        } 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 see that all objects are associated by the AssociationsManagermanagement

class AssociationsManager {
    // associative references: object pointer -> PtrPtrHashMap.
    static AssociationsHashMap *_map;
public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    
    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

We see all the objects associated objects are stored in static AssociationsHashMap in. In object as key, to obtain a further AssociationsHashMap, which stores the key-on key-value

The above problem seems to be the only one, when the associated object is destroyed it?
By searching keyword, we found our desired content in objc-runtime-new.mm file

/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory. 
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

Through the above comments and code, we clearly understand the order of release. In fact, the associated object is placed in the last step, indicating that the release of the associated object is released much later than the object.

So far, the topic of Category come to an end, which involves more of the source content, so he spent a lot more time. Finally, thanks again in-depth understanding-C in Objective: the Category , in a very detailed.

Guess you like

Origin blog.csdn.net/weixin_34026484/article/details/90968586