美文网首页
Category探究

Category探究

作者: 飞奔的小鲨鱼 | 来源:发表于2018-12-17 00:12 被阅读0次

    Category简介

    category是Objective-C 2.0之后添加的语言特性,category的主要作用是为已经存在的类添加方法。除此之外,apple还推荐了category的另外两个使用场景。使用场景

    • 可以把类的实现分开在几个不同的文件里面。这样做有几个显而易见的好处:
    1. 可以减少单个文件的体积
    2. 可以把不同的功能组织到不同的类别里
    3. 可以由多个开发者共同完成一个类
    4. 可以按需加载想要的类别等等
    • 声明私有方法

    Category和Extension

    • extension
      1> 编译期决议,他是类的一部分。
      2>一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加扩展,所以你无法为系统的类比如NSString添加扩展。
      3>可以添加实例变量

    • category
      1> 运行期决议。
      2>无法添加实例变量

    Category底层

    在objc-runtime-new.h中可以找到category_t,本文中用到的runtime源码是objc4-750.tar.gz

    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_t结构体的定义可以看出,可以添加实例方法,类方法,协议,添加属性 ,但是不能添加实例变量。

    为了弄清楚它的底层实现,我们先创建Person类和它的分类

    @interface Person : NSObject
    @property (nonatomic, assign) NSInteger age;
    @property (nonatomic, copy)   NSString * name;
    - (void)work;
    @end
    @implementation Person
    - (void)work{
        NSLog(@"Person --> work");
    }
    @end
    
    @interface Person (category1)
    @property (nonatomic, copy) NSString * address;
    - (void)work;
    - (void)work1;
    @end
    @implementation Person (category1)
    - (void)work{
        NSLog(@"Person (category1) --> work");
    }
    
    - (void)work1{
        NSLog(@"Person (category1) --> work1");
    }
    @end
    

    进入相应的文件夹,在终端执行clang -rewrite-objc Person+category1.m,得到一个9w+多行的文件,在文件的最下面

    static struct /*_method_list_t*/ {
        unsigned int entsize;               // sizeof(struct _objc_method) 内存的大小
        unsigned int method_count;          // 方法的数量
        struct _objc_method method_list[2]; // 对象方法的列表
    } 
    _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_category1 
    __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        2,
        {
    {(struct objc_selector *)"work", "v16@0:8", (void *)_I_Person_category1_work},
    {(struct objc_selector *)"work1", "v16@0:8", (void *)_I_Person_category1_work1}
        }
    };
    
    static struct /*_method_list_t*/ {
        unsigned int entsize;               // sizeof(struct _objc_method) 内存的大小
        unsigned int method_count;          // 方法的数量
        struct _objc_method method_list[1]; // 类方法的列表
    } _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_category1 
    __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {
    {(struct objc_selector *)"category1", "v16@0:8", (void *)_C_Person_category1_category1}
        }
    };
    
    struct _objc_method {
        struct objc_selector * _cmd;
        const char *method_type;
        void  *_imp;
    };
    

    我们发现这两个结构体都是_method_list_t类型的,结构体中存放着内存的大小,方法的数量以及方法的列表,一个是对象方法,另一个是类方法,并分别进行赋值。

    struct _protocol_t {
        void * isa;  // NULL
        const char *protocol_name;
        const struct _protocol_list_t * protocol_list; // super protocols
        const struct method_list_t *instance_methods;
        const struct method_list_t *class_methods;
        const struct method_list_t *optionalInstanceMethods;
        const struct method_list_t *optionalClassMethods;
        const struct _prop_list_t * properties;
        const unsigned int size;  // sizeof(struct _protocol_t)
        const unsigned int flags;  // = 0
        const char ** extendedMethodTypes;
    };
    
    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[1];
    } _OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying 
    __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", 0}}
    };
    
    struct _protocol_t _OBJC_PROTOCOL_NSCopying __attribute__ ((used)) = {
        0,
        "NSCopying",
        0,
        (const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying,
        0,
        0,
        0,
        0,
        sizeof(_protocol_t),
        0,
        (const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCopying
    };
    struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSCopying = &_OBJC_PROTOCOL_NSCopying;
    
    static struct /*_protocol_list_t*/ {
        long protocol_count;  // Note, this is 32/64 bit
        struct _protocol_t *super_protocols[1];
    } _OBJC_CATEGORY_PROTOCOLS_$_Person_$_category1 
    __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        1,
        &_OBJC_PROTOCOL_NSCopying
    };
    

    首先我们看到_protocol_list_t中有协议的数量_protocol_t类型的结构体,在赋值的时候将1和_OBJC_PROTOCOL_NSCopying的地址赋给了_protocol_list_t结构体,而_OBJC_PROTOCOL_NSCopying_protocol_t类型的结构体,一一对应赋值,在为instance_methods赋值的时候调用了_OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying,这是一个_method_list_t结构体类型,里面保存着要实现的协议方法copyWithZone

    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_Person_$_category1 
    __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        1,
        {{"address","T@\"NSString\",C,N"}}
    };
    
    struct _prop_t {
        const char *name;       // 属性的名称
        const char *attributes; // 属性的特质
    };
    

    然后就是_prop_list_t结构体了,分别是占用的内存大小,属性的数量和_prop_t类型的结构体,"T@"NSString" NSString类型,C:copy,N:Nonatomic。

    Category的加载

    在objc-os.mm中875行可以找到_objc_init

    void _objc_init(void)
    {
        static bool initialized = false;
        if (initialized) return;
        initialized = true;
      
        // fixme defer initialization until an objc-using image is found?
        environ_init();
        tls_init();
        static_init();
        lock_init();
        exception_init();
    
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    

    objc-runtime-new.mm文件中找到了map_images,load_images,unmap_imagemap_images内部会调用map_images_nolock函数,而map_images_nolock函数又会调用_read_images函数,在_read_images函数的最后找到分类相关代码:

    // Discover categories. 
        for (EACH_HEADER) {
        // 循环遍历每一个类,取出当前类category_t的数组
            category_t **catlist = 
                _getObjc2CategoryList(hi, &count);
            bool hasClassProperties = hi->info()->hasCategoryClassProperties();
          // 遍历每一个类的category_t的数组,取出每一个category_t
            for (i = 0; i < count; i++) {
                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) 
                {
                    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);
                    }
                }
            }
        }
    
        ts.log("IMAGE TIMES: discover categories");
    

    通过_getObjc2CategoryList方法获得category的列表,然后遍历列表将category的实例方法,协议以及属性添加到类上,将category的类方法和协议添加到类的元类上。

    static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                              header_info *catHeader)
    {
        runtimeLock.assertLocked();
    
        // 返回一个未添加的category的哈希表
        NXMapTable *cats = unattachedCategories();
        category_list *list;
    
        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[list->count++] = (locstamped_category_t){cat, catHeader};
        NXMapInsert(cats, cls, list);
    }
    
    static void remethodizeClass(Class cls)
    {
        category_list *cats;
        bool isMeta;
    
        runtimeLock.assertLocked();
    
        isMeta = cls->isMetaClass();
    
        // Returns the list of unattached categories for a class 
        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--) {
            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);
    }
    

    首先根据方法列表,属性列表,协议列表分配内存,根据多少个分类以及每一块方法需要多少内存来分配相应的内存地址,接着循环添加分类方法,属性以及协议放入对应mlist、proplists、protolosts数组中,调用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拷贝到原来数组的前边
                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]));
            }
        }
    

    在这个方法中将原来方法列表经过内存移动,再将category对应的方法列表拷贝到原来方法列表的前边,合并成一个新的方法列表,这也就是category重写了原来的方法会优先调用category方法的原因,只是在合并的时候将category的方法放在了前边,如果继续查找的话,就会找到原来类的方法。

    深入理解的Objective-C:类别
    探秘Runtime - 深入剖析Category

    相关文章

      网友评论

          本文标题:Category探究

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