Category的实现原理

什么是category?

是在不改变已存在类的情况下,对其添加方法来达到对类进行功能扩展的目的。

category结构

可以在 objc-runtime-new.h 文件中看到 category_t 结构体的定义

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; //实例属性列表
    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 的实现原理

在源码 objc-runtime-new.mm 文件中的第 2560 行开始,可以看到关于 category 的处理逻辑:

for (EACH_HEADER) {
         // 1.获取category列表
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count); 
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        // 2.遍历
        for (i = 0; i < count; i++) {   
            category_t *cat = catlist[i];
            // 3.对应的类cls
            Class cls = remapClass(cat->cls);   
             // 3.1 如果cls为nil
            if (!cls) {     
                 // 3.2 将category置为nil
                catlist[i] = nil;  
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }
            
            bool classExists = NO;
            // 4. 实例方法 或 协议 或 属性存在
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    // 4.1 处理category中的数据(类)
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

            // 5. 类方法 或 协议 或 属性存在
            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    // 5.1 处理category中的数据(元类)
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }
    
复制代码

处理category中的数据又是在方法remethodizeClass()中实现,注意传入的cls参数,如果是实例方法则传入的是类cls,如果是类方法,传入的是cls的isa指针,也就是元类,跟踪该方法:

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

    runtimeLock.assertWriting();

    // 1. 是否为元类
    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)" : "");
        }
        // 2. 修改对应的类的方法列表、协议列表、属性列表
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}
复制代码

修改对应的类或元类中的方法列表、协议列表、属性列表又是在attachCategories()方法内实现的,跟踪该方法:

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // 1.创建数组
    // 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;
    // 2. 倒叙插入
    while (i--) {  
        auto& entry = cats->list[i];

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

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

        // 2.3 协议列表
        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);
}
复制代码

创建一个数组,倒叙插入 新增的方法、属性和协议、具体的插入实现在方法attachLists()中,跟踪该方法

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

        // 1.主类中有多个数据集合的时候
        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            
            // 1.1 使用realloc() 函数将原来的空间拓展
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            
            // 1.2 将原来的数组复制到后面
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
                    
            // 1.3 把新数组复制到前面
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        
        //2.为空的时候,直接将指针指向新的数据集。
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        }
        
        // 3. 主类中只有一个数据集合的时候
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            
            // 3.1 使用malloc()重新申请一块内存
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            
            // 3.2 将原来的集合放到最后
            if (oldList) array()->lists[addedCount] = oldList;
            
            // 3.3 新数组复制到前边
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }
复制代码

如上缩减,runtime 针对主类中原有列表的三种情况,有三种不同的插入处理。

小结

  1. 当category中方法和类中的方法同名的话,会优先调用category中的方法
  2. 这里看起来是category的方法覆盖了主类的方法,但实际是category的方法在方法列表的前面,所以总先调用category中的方法。
  3. 多个category中存在相同的方法时,调用的顺序跟编译的顺序有关,最后一个编译的,会被调用。
category为什么可以新增方法,而不能新增属性呢?

回头仔细看下 obj_class 的结构:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    
    / ************** /
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    / ************** /
    
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

复制代码

注意看 ivars 和 methodLists 有什么区别?
ivars是指向 objc_ivar_list 的指针,而 methodLists 是指 向objc_method_list 的指针的指针。
objc_class 结构体的大小是固定的,不能添加数据,只能修改。 所以 ivars 指向的 objc_ivar_list 是一个固定区域,只能修改成员变量的值,不能增加成员变量的个数;
虽然没办法扩展methidLists指向的区域,但是可以修改 *objc_method_list 的值来增加成员变量;

给category增加属性 : 关联对象(Associated Object)

虽然可以实现,但是不推荐在category中添加属性

在 .h 文件中添加属性的声明

@property (copy, nonatomic) NSString * addName;
复制代码

在 .m 中

#import "NSObject+addProperty.h"
#import <objc/runtime.h>

@implementation NSObject (addProperty)

static char *addNameKey = "addNameKey";

-(void)setAddName:(NSString *)addName{
    /* 关联方法:
     * id object 给哪个对象的属性赋值
     * const void *key 属性对应的key
     * id value  设置属性值为value
     * objc_AssociationPolicy policy  使用的存储策略,copy/retain/assign
     */
    objc_setAssociatedObject(self, addNameKey, addName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)addName{
    return objc_getAssociatedObject(self, addNameKey);
}


@end
复制代码

关联对象(Associated Object)

关联对象可以被理解为 Runtime 中的字典

objc_setAssociatedObject 相当于 setValue:forKey 进行关联value对象
关联对象与被关联对象本身的存储并没有直接的关系,它是存储在单独的哈希表中的\

objc_getAssociatedObject 用来读取对象
objc_removeAssociatedObjects函数来移除一个关联对象
或者使用objc_setAssociatedObject函数将key指定的关联对象设置为nil。

转载于:https://juejin.im/post/5b65113de51d453509564a33

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值