美文网首页
OC基础-category(1)

OC基础-category(1)

作者: 我是卖报的小行家 | 来源:发表于2021-03-04 10:25 被阅读0次

首先我们要明白什么是category?
类扩展class extension
我们有一个ZKPerson类如下里面会有属性,以及instance方法和class方法

@interface ZKPerson()

@property (nonatomic, assign)int height;

@end
@implementation ZKPerson

- (void)run{
    NSLog(@"run");
}

+ (void)run2{
    NSLog(@"+ run2");
}

@end

那什么是类扩展呢
类扩展和分类Catgory一样都是可以给类增加定义属性和方法。
我们新建ZKPerson两个分类(Test) (Eat)如下

@interface ZKPerson (Test)

- (void)run;

- (void)test;

+ (void)test2;


@end
@implementation ZKPerson (Test)

- (void)run{
    NSLog(@"ZKPerson (Test) -run");
}

- (void)test{
    NSLog(@"ZKPerson (Test) -test");
}

+ (void)test2{
    NSLog(@"ZKPerson (Test)+ test2");
}

@end
@interface ZKPerson (Eat)<NSCoding,NSCopying>

- (void)run;

- (void)eat;

+ (void)eat2;

@property (nonatomic, assign)double height;

@property (nonatomic, assign)int weight;

@end
@implementation ZKPerson (Eat)

- (void)run{
    NSLog(@"ZKPerson (Eat) -run");
}

- (void)eat{
    NSLog(@"ZKPerson (Eat) -eat");
}

+ (void)eat2{
    NSLog(@"ZKPerson (Eat)+ eat2");
}

@end

我们再将ZKPerson+(Eat)转化成.cpp文件
(xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ZKPerson+Eat.m),以便查看category的底层。
我们会发现category底层结构为一个_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;//属性列表
};

也就是说当我们每创建一个分类,系统在编译时候就会把这个分类转化成这样一个结构体并保存起来。

当我们在程序运行时runtime,所有分类的这些属性方法等将会合并到Person里面,分类的对象方法合并到Person类对象里面,分类的类方法合并到Person元类对象里面。runtime合并过程源码如下:

//cls传进去的有可能是类对象也可能是元类对象,cls = [ZKPerson class] //   
        class_getName( [ZKPerson class])
//cats传进去的是分类数组,有几个分类数组里面就有几个 cats = [category_t(Test), category_t(Eat)];//每个分类编译时实际上是通过category_t结构体保存的
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
    //方法数组(2个类扩展Eat和Test)
    /*
     [
        [method_t,method_t],//其中一个分类的方法列表比如(Test)
        [method_t,method_t],//其中另外一个分类的方法列表比如(Eat)
     ]
     */
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    //属性数组
    /*
    [
       [property_t,property_t],//其中一个分类的属性列表
       [property_t,property_t],
    ]
    */
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    //协议数组
    /*
    [
       [protocol_t,protocol_t],//其中一个分类的协议列表
       [protocol_t,protocol_t],
    ]
    */
    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--) {
        //取出某个分类category_t
        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);
}

查看下attachLists是具体如何实现将分类方法加到类对象的方法列表中(原来的方法列表往后移,新的分类的方法列表放在原来的方法列表前面)

/*
     addedLists
     [
        [method_t, method_t],
        [method_t, method_t],
     ]
     addedCount
     2
     */
    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:原来的方法列表往后移,新的分类的方法列表放在原来的方法列表前面
            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]));
        }
    }

就此,我们可以看到后面合并进来的分类方法列表是放在数组的前面的,最后把所有分类的方法列表合并到原有类的方法列表前面。也就是说后编译的分类数据会在数组的前面,当我们调用时会优先调用数组前面的方法。
我们可以实践一下,编译前记得在Target-BuildPhase-Compile Sources中删除我们的.cpp文件,不让它参与编译,我们调用[person run]实例方法,首先我们的编译顺序是这样的,先编译Test分类,然后时Eat分类,最后是Person类


打印结果 编译顺序

因为Eat分类是最后编译的,所以调用的是Eat分类的run方法。虽然Person类是最后编译,但是从上述源码中看到所有分类的方法合并时是放在Person类方法列表前面的。不管Person类何时编译,只要它有分类就会把分类的方法列表合并到Person类的方法列表前面。所以我们调用时分类会覆盖Person类的方法。

总接:
1、什么时候用到category
给系统类添加方法、属性(需要关联对象)。
对某个类大量的方法,可以实现按照不同的名称归类。
2、category的实现原理
答:category编译之后的底层结构是struct category_t,里面存储着分类的对象方法,类方法,属性,协议信息,在程序运行的时候,runtime会将category的数据,合并到原有类信息中(类对象,元类对象中)

3、category的加载处理过程
答:
1)、通过Runtime加载某个类的所有category数据
2)、把所有category的方法,属性,协议数据,合并到一个大数组中,后面参与编译的category数据,会在数组的前面
3)、将合并后的分类数据(方法,属性,协议),插入到类原来数据的前面
4)、分类的调用顺序是后编译的先调用

3、category和类扩展class extension的区别?
答:category是在运行时将方法属性合并到类对象里面,而类扩展是在编译的时候就将方法属性等合并到类对象里面

内容转自于此篇华文https://www.jianshu.com/p/9e77093ed6c6

相关文章

网友评论

      本文标题:OC基础-category(1)

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