美文网首页
iOS原理(三)----CateGory

iOS原理(三)----CateGory

作者: 会笑的Even | 来源:发表于2018-12-27 00:44 被阅读0次

    iOS原理(三)----CateGory

    创建一个Animal,及其两个分类,并调用其eat,run,sleep方法.

    @interface Animal : NSObject
    
    - (void)eat;
    
    @end
    
    @implementation Animal
    
    - (void)eat {
        NSLog(@"---eat---");
    }
    
    @end
    
    @interface Animal (Run)
    
    - (void)run;
    
    @end
    
    @implementation Animal (Run)
    
    - (void)run {
         NSLog(@"---run---");
    }
    
    @end
    
    @interface Animal (Sleep)
    
    - (void)sleep;
    
    @end
    
    @implementation Animal (Sleep)
    
    - (void)sleep {
        NSLog(@"---sleep---");
    }
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Animal *ani = [[Animal alloc] init];
            [ani eat];
            [ani run];
            [ani sleep];
        }
        return 0;
    }
    

    用命令生成C++变异文件:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp;

    每个分类都会转成一个_category_t类型的结构体,

    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 _category_t _OBJC_$_CATEGORY_NSObject_$_Run __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "NSObject",
        0, // &OBJC_CLASS_$_NSObject,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_Run,
        0,
        0,
        0,
    };
    
    static struct _category_t _OBJC_$_CATEGORY_NSObject_$_Sleep __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "NSObject",
        0, // &OBJC_CLASS_$_NSObject,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_Sleep,
        0,
        0,
        0,
    };
    
    

    最后把该类的所有分类放在:

    static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [2] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
        &_OBJC_$_CATEGORY_NSObject_$_Run,
        &_OBJC_$_CATEGORY_NSObject_$_Sleep,
    };
    

    到底何时将分类的信息添加到原本的类当中的呢?查看苹果源码片段如下:

    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);
    }
    
    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;
                // 将原来的array()->lists后移,大小为addedCount
                memmove(array()->lists + addedCount, array()->lists, 
                        oldCount * sizeof(array()->lists[0]));
                // 将新增的addedLists,添加到array()->lists中,从开头开始添加,大小为addedCount
                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]));
            }
        }
    

    从上面的代码看出,在类初始化的利用runtime将分类属性,方法,协议添加到原来类当中.

    在之前的Rnu,Sleep分类中的分别实现Animal中的eat方法,我们调用eat方法,那到底会调那个eat方法.实际调用如下:

    Snip20181110_1.png

    调用了Run分类的eat方法.为什么会调用Run分类的eat方法?

    此时编译的顺序为:

    Snip20181110_4.png

    改变Run分类和Sleep分类的编译顺序,

    Snip20181110_5.png

    此时调用为Sleep分类的eat方法:

    Snip20181110_6.png

    这是因为又前面的源码得知,当app按顺序编译后,运行的时候mlists是倒序添加的分类方法列表,调用attachLists()函数将mlists所有新增方法,放在array()->lists的开始.所以最后编译的分类方法,运行时添加在array()->lists的最前面.

    所以Category的原理是:

    • 编译时每个分类生成_category_t类型的结构体.
    • 通过Runtime加载某个类的所有Category数据.
    • 把所有Category的方法、属性、协议数据,合并到一个大数组中(后面参与编译的Category数据,会在数组的前面).
    • 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面.

    _category_t并没有存放成员变量,所有我们在分类中并不能直接添加成员变量,但是我们可以间接用关联对象添加成员变量.比如我们给Run分类添加一个height属性(只生成setter,getter声明,不提供实现),方法如下:

    void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)

    其中policy有四个值,

    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
        // 相当于assign
        OBJC_ASSOCIATION_ASSIGN = 0, 
        // 相当于strong, nonatomic         
        OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
       //  相当于copy, nonatomic
        OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 
        // 相当于strong, atomic  
        OBJC_ASSOCIATION_RETAIN = 01401,
        // 相当于copy, atomic
        OBJC_ASSOCIATION_COPY = 01403         
       };
    

    setter实现如下:

    - (void)setHeight:(double)height {
        objc_setAssociatedObject(self, @"height", @(height), OBJC_ASSOCIATION_ASSIGN);
    }
    

    getter实现如下:

    - (double)height {
        return [objc_getAssociatedObject(self, @"height") doubleValue];
    }
    

    由前面的得知关联对象我们并没有实际生成成员变量,那到底底层实际是如何实现的呢?查看runtime源码,void objc_setAssociatedObject()代码如下:

    void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
        _object_set_associative_reference(object, (void *)key, value, policy);
    }
    
    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中参数中的DISGUISE(object)为key,值为ObjectAssociationMap
            AssociationsHashMap &associations(manager.associations());
            disguised_ptr_t disguised_object = DISGUISE(object);
            // value不为nil
            if (new_value) {
                AssociationsHashMap::iterator i = associations.find(disguised_object);
                
                if (i != associations.end()) {
                    // ObjectAssociationMap已经存在时
                    ObjectAssociationMap *refs = i->second;
                    ObjectAssociationMap::iterator j = refs->find(key);
                    if (j != refs->end()) {
                        // value已经存在ObjectAssociationMap时
                        old_association = j->second;
                        j->second = ObjcAssociation(policy, new_value);
                    } else {
                        // value没有存在ObjectAssociationMap时
                        (*refs)[key] = ObjcAssociation(policy, new_value);
                    }
                } else {
                    // ObjectAssociationMap不存在时
                    ObjectAssociationMap *refs = new ObjectAssociationMap;
                    associations[disguised_object] = refs;
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                    object->setHasAssociatedObjects();
                }
                
            } else {
                // 值为nil的时候,相当于移除关联关系
                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);
    }
    

    从上面源码得知:整个关联对象由AssociationsManager类管理,里面有一个map为AssociationsHashMap,key为由object参数生成的DISGUISE(object),值为一个map为ObjectAssociationMap,key为参数的key,值为参数的value.

    Snip20181111_16.png

    当移除某个key的关联关系时,直接将参数value设置为nil.

    获取关联对象的值的源码为:

    id objc_getAssociatedObject(id object, const void *key) {
        return _object_get_associative_reference(object, (void *)key);
    }
    
    id _object_get_associative_reference(id object, void *key) {
        id value = nil;
        uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
        {
            AssociationsManager manager;
            AssociationsHashMap &associations(manager.associations());
            disguised_ptr_t disguised_object = DISGUISE(object);
            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()) {
                    ObjcAssociation &entry = j->second;
                    value = entry.value();
                    policy = entry.policy();
                    if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
                        objc_retain(value);
                    }
                }
            }
        }
        if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
            objc_autorelease(value);
        }
        return value;
    }
    
    

    知道了objc_setAssociatedObject()原理之后,也很容易理解objc_getAssociatedObject()的代码.

    如果要移除object的所有关联关系,函数为void objc_removeAssociatedObjects(id object),其实就是移除AssociationsManagerAssociationsHashMap对应由DISGUISE(object)作为key的值,就是ObjectAssociationMap.

    void objc_removeAssociatedObjects(id object) 
    {
        if (object && object->hasAssociatedObjects()) {
            _object_remove_assocations(object);
        }
    }
    
    void _object_remove_assocations(id object) {
        vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
        {
            AssociationsManager manager;
            AssociationsHashMap &associations(manager.associations());
            if (associations.size() == 0) return;
            disguised_ptr_t disguised_object = DISGUISE(object);
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // 复制所有需要移除的关联对象
                ObjectAssociationMap *refs = i->second;
                for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                    elements.push_back(j->second);
                }
                delete refs;
                associations.erase(i);
            }
        }
        for_each(elements.begin(), elements.end(), ReleaseValue());
    }
    

    相关文章

      网友评论

          本文标题:iOS原理(三)----CateGory

          本文链接:https://www.haomeiwen.com/subject/gssllqtx.html