Objectiive-C Category

本文主要介绍Objectiive-C的Category,当你需要扩展系统SDK提供的类的时候,Category就十分有用.Category允许你为已经存在的类添加属性或者方法,供自己使用.

Category添加的属性和方法是可以供类和其子类使用的.

1.Category 添加实例方法,类方法和属性:

@interface UIView (TagID)

-(void)logUIView;

+(void)debugLogUIView;

@property (nonatomic,copy)NSString * tagName;

@end

-(void)setTagName:(NSString *)tagName{

    objc_setAssociatedObject(self, "tagName", tagName, OBJC_ASSOCIATION_COPY);

}

-(NSString *)tagName{

  return objc_getAssociatedObject(self, "tagName");

}

-(void)logUIView{

    NSLog(@"self-->%@ infomation-->%@",[self class],[self description]);

}

+(void)debugLogUIView{


    NSLog(@"self-->%@ info-->%@",self,[self debugDescription]);

}

2.Category 是不能添加实例变量的,因为category是在运行时才会去添加,此时对象的内存布局已经确定,不能添加实例变量.

3.Objectiive-C的Category 实现原理

Runtime源码中Category的结构:

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;

    method_list_t *methodsForMeta(bool isMeta) {

        if (isMeta) return classMethods;

        else return instanceMethods;

    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);

};

可以看到Category 的结构体中存在名字,类,实例方法,类方法,协议,实例属性,类属性,还有Category结构体自带的方法methodsForMeta(该方法用于返回Categroy的方法).

同时Runtime会把Categroy添加到类上

static void

attachCategories(Class cls, category_list *cats, bool flush_caches)

{

    if (!cats) return;

    if (PrintReplacedMethods) printReplacements(cls, cats);

//  获取当前类是不是MetaClass

    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));

//循环把Categroy加到类上

    // 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];

//      获取Categroy方法列表

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);

        if (mlist) {

            mlists[mcount++] = mlist;

            fromBundle |= entry.hi->isBundle();

        }

//获取Categroy属性列表

        property_list_t *proplist =

            entry.cat->propertiesForMeta(isMeta, entry.hi);

        if (proplist) {

            proplists[propcount++] = proplist;

        }

//获取Categroy协议列表

        protocol_list_t *protolist = entry.cat->protocols;

        if (protolist) {

            protolists[protocount++] = protolist;

        }

    }

//获取类的符号结构

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);

//    将Catrgroy的方法列表添加到类上(copy到类的方法列表数组内部)

    rw->methods.attachLists(mlists, mcount);

    free(mlists);

    if (flush_caches  &&  mcount > 0) flushCaches(cls);

//将Catgroy的属性列表添加到类上(copy到类的方法列表数组内部)

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

    free(proplists);

//将Catgroy的协议列表添加到类上(copy到类的方法列表数组内部)

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

    free(protolists);

}

下面这段代码就是attachLists的实现

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;

//            将原来的数组移动到新数组的addedCount位置之后

            memmove(array()->lists + addedCount, array()->lists,

                    oldCount * sizeof(array()->lists[0]));

//            将addedLists copy到新数组的首位开始的位置

            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;

//            将addedLists copy到新数组的首位开始的位置

            memcpy(array()->lists, addedLists,

                  addedCount * sizeof(array()->lists[0]));

        }

    }

category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会停止查找.

关于Categroy的关联对象

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);

//    如果value存在根据policy发送SEL_retain 或SEL_copy

    id new_value = value ? acquireValue(value, policy) : nil;

    {

//        获取到AssociationsManager的hashMap这是储存所有关联对象的hashMap

        AssociationsManager manager;

        AssociationsHashMap &associations(manager.associations());

        disguised_ptr_t disguised_object = DISGUISE(object);

        if (new_value) {

            // break any existing association.

//            迭代hashMap并存储关联的对象

            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);

}

Runtime采用的是全局的HashMap存储关联对象.

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容