美文网首页
category学习笔记

category学习笔记

作者: nunca | 来源:发表于2018-11-20 19:04 被阅读0次

    1.category 是什么?

    首先,新建一个NcFood类,并添加两个分类

    #import <Foundation/Foundation.h>
    
    @interface NcFood : NSObject
    
    - (void)color;
    
    @end
    
    @interface NcFood (Apple)
    
    @property(nonatomic,strong)NSString *name;
    
    @end
    
    @interface NcFood (Mango)
    
    @property(nonatomic,strong)NSString *name;
    @property(nonatomic,assign)int amount;
    
    @end
    
    #import "NcFood.h"
    
    @implementation NcFood
    
    - (void)color {
        NSLog(@"color");
    }
    
    @end
    
    
    @implementation NcFood (Apple)
    
    - (void)color {
        NSLog(@"pink");
    }
    
    @end
    
    @implementation NcFood (Mango)
    
    - (void)color {
        NSLog(@"golden yellow");
    }
    
    @end
    

    用Clang编译成c++文件

    终端输入:
     clang -rewrite-objc Ncfood.m
    

    忽略不用的信息,先查看编译后的category的结构

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

    再看编译后的分类Apple与Mango

    Apple:

    // 实例方法列表
    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_NcFood_$_Apple __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"color", "v16@0:8", (void *)_I_NcFood_Apple_color}}
    };
    
    // 属性列表
    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_NcFood_$_Apple __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        1,
        {{"name","T@\"NSString\",&,N"}}
    };
    
    // 分类结构体
    static struct _category_t _OBJC_$_CATEGORY_NcFood_$_Apple __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "NcFood",
        0, // &OBJC_CLASS_$_NcFood,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NcFood_$_Apple,
        0,
        0,
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NcFood_$_Apple,
    };
    
    //  此处将_NcFood类的地址赋给Apple分类结构体中的cls
    static void OBJC_CATEGORY_SETUP_$_NcFood_$_Apple(void ) {
        _OBJC_$_CATEGORY_NcFood_$_Apple.cls = &OBJC_CLASS_$_NcFood;
    }
    

    Mango:

    // 实例方法列表
    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_NcFood_$_Mango __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"color", "v16@0:8", (void *)_I_NcFood_Mango_color}}
    };
    
    // 属性列表
    static struct /*_prop_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count_of_properties;
        struct _prop_t prop_list[2];
    } _OBJC_$_PROP_LIST_NcFood_$_Mango __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        2,
        {{"name","T@\"NSString\",&,N"},
        {"amount","Ti,N"}}
    };
    
    // 分类结构体
    static struct _category_t _OBJC_$_CATEGORY_NcFood_$_Mango __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "NcFood",
        0, // &OBJC_CLASS_$_NcFood,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NcFood_$_Mango,
        0,
        0,
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NcFood_$_Mango,
    };
    
    //  此处将_NcFood类的地址赋给Mango分类结构体中的cls
    static void OBJC_CATEGORY_SETUP_$_NcFood_$_Mango(void ) {
        _OBJC_$_CATEGORY_NcFood_$_Mango.cls = &OBJC_CLASS_$_NcFood;
    }
    

    另外还有一点需要关注的

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

    所有的分类都会被保存在这个表里,在程序启动运行的时候会遍历这个列表,将其中的category与它所属的类关联。

    编译后的category就是这个样子了

    关于category的属性

    以上两个分类(Apple与Mango)都有自己的属性,但在实例方法列表中并没有看到相应的set与get方法,分类的结构体中也没有用来存储成员变量的地方,所以分类中的属性没有实现set与get方法,也没有自动生成成员变量。
    用点语法访问分类的属性会导致程序崩溃。(点语法本质就是调用set、get方法)

        NcFood *food = [[NcFood alloc] init];
        NSLog(@"---%@",food.name); // 此处崩溃
    
    
    2018-11-20 17:10:45.419760+0800 Nunca[39382:6373286] -[NcFood name]: unrecognized selector sent to instance 0x600000008860
    

    2. category的加载

    category的加载发生在程序启动后调用的map_images函数中(objc-runtime-new.mm类中)
    在map_images里面,在加载category之前还会先加载好Class、_Protocol。

    以下是其中加载分类的代码

    // Discover categories. 
        for (EACH_HEADER) {
            //取出编译时生成的一个包含程序中所有category的list
            category_t **catlist = 
                _getObjc2CategoryList(hi, &count);
            bool hasClassProperties = hi->info()->hasCategoryClassProperties();
    
            for (i = 0; i < count; i++) { // 遍历每一个category
                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) 
                {
                   // 在此方法中将分类存到类对应的其分类的list(类与类的分类可能是一对多的)
                    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);
                    }
                }
            }
        }
    

    主要需要关注两个方法

    addUnattachedCategoryForClass :将新遍历到的category添加至还未与类进行关联的对应的category_list

    static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                              header_info *catHeader)
    {
        runtimeLock.assertWriting();
    
        // DO NOT use cat->cls! cls may be cat->cls->isa instead
        
       //cats是一个以cls为key、以category_list为value的map
        NXMapTable *cats = unattachedCategories();
        category_list *list;
        // 根据cls获取还未进行关联的分类列表
        list = (category_list *)NXMapGet(cats, cls);
        if (!list) {
            list = (category_list *)
                calloc(sizeof(*list) + sizeof(list->list[0]), 1);
        } else {
            list = (category_list *)
                realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
        }
         // 往未进行关联的分类列表中添加新的分类
        list->list[list->count++] = (locstamped_category_t){cat, catHeader};
        NXMapInsert(cats, cls, list);
    }
    

    remethodizeClass :将分类中的各种方法整合至类的方法列表

    static void remethodizeClass(Class cls)
    {
        category_list *cats;
        bool isMeta;
    
        runtimeLock.assertWriting();
    
        isMeta = cls->isMetaClass();
    
        // Re-methodizing: check for more categories
        // unattachedCategoriesForClass里面会返回类对应的分类列表,并将其从map中移除
        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);
        }
    }
    
    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--) {
            // 倒序遍历,编译越后的category,在list中越靠前
            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,下面摘录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;
                // 将原有的方法往后移
                memmove(array()->lists + addedCount, array()->lists, 
                        oldCount * sizeof(array()->lists[0]));
                // 将新加的方法添加至最前面
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));
            }
     }
    
    关于分类的调用顺序

    可以看到,新添加的分类的方法会在类的方法的最前部。
    越后编译的分类的方法在类的方法列表中位置越靠前,因此,如果一个类与它的分类中存在相同的方法,在调用此方法时,会从它的类的方法列表去查找,最后被编译的在列表的最前面,因此被调用的也是最后被编译的那个分类中的那个方法(即使此分类的头文件未被导入至方法调用的类)。
    (编译的顺序往往是我们不太关心确定的,因此最好不要在同一个类的多个分类中写同名的方法)

    主要参考:
    深入理解Objective-C:Category

    相关文章

      网友评论

          本文标题:category学习笔记

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