美文网首页
runtime源码分析之category的实现

runtime源码分析之category的实现

作者: 充满活力的早晨 | 来源:发表于2018-04-18 17:27 被阅读175次

    category简介

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

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

    category真面目

    所有的OC类和对象,在runtime层都是用struct表示的,category也不例外,在runtime层,category用结构体category_t(在objc-runtime-new.h中可以找到此定义)

    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;
    
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
    
        property_list_t *propertiesForMeta(bool isMeta) {
            if (isMeta) return nil; // classProperties;
            else return instanceProperties;
        }
    };
    

    成员变量

    1)、类的名字(name)
    2)、类(cls)
    3)、category中所有给类添加的实例方法的列表(instanceMethods)
    4)、category中所有添加的类方法的列表(classMethods)
    5)、category实现的所有协议的列表(protocols)
    6)、category中添加的所有属性(instanceProperties)

    方法

    method_list_t *methodsForMeta(bool isMeta)
    根据传入是是不是元类来返回响应的值。

    property_list_t *propertiesForMeta(bool isMeta)
    这个就是判断是否是元类返回响应的属性,元类是没有属性的。

    代码重编译

    我们先写个category 类,我们用clang -rewrite-objc xx.m 编译这个catergory

    #import <Foundation/Foundation.h>
    
    @interface CategoryObject : NSObject
    
    @end
    
    @interface CategoryObject(MyAddition)
    
    @property(nonatomic, copy) NSString *name;
    @property(nonatomic, copy) NSString *value;
    
    - (void)printName;
    -(void)printValue;
    @end
    @interface CategoryObject(MyAddition2)
    @property(nonatomic, copy) NSString *name;
    @property(nonatomic, copy) NSString *age;
    @property(nonatomic, copy) NSString *address;
    - (void)printName;
    -(void)printAge;
    -(void)printAddress;
    
    @end
    
    
    #import "CategoryObject.h"
    
    @implementation CategoryObject
    - (void)printName
    {
        NSLog(@"%@",@"CategoryObject");
    }
    @end
    
    @implementation CategoryObject(MyAddition)
    
    - (void)printName
    {
        NSLog(@"printName %@",@"MyAddition");
    }
    -(void)printValue{
        NSLog(@"printValue %@",@"MyAddition");
    }
    
    @end
    
    @implementation CategoryObject(MyAddition2)
    -(void)printAge{
        NSLog(@"printAge %@",@"MyAddition2");
    
    }
    -(void)printAddress{
        NSLog(@"printAddress %@",@"MyAddition2");
    
    }
    - (void)printName
    {
        NSLog(@"printName %@",@"MyAddition2");
    }
    
    @end
    
    

    这里我们定义了两个category ,并且每一个category中都有方法和属性。
    重新编译后的文件很多,我们就摘抄与我们有关的部分。
    我们写的所有代码都是在文件最后面

    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_CategoryObject_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        2,
        {{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_CategoryObject_MyAddition_printName},
        {(struct objc_selector *)"printValue", "v16@0:8", (void *)_I_CategoryObject_MyAddition_printValue}}
    };
    
    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_CategoryObject_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        2,
        {{"name","T@\"NSString\",C,N"},
        {"value","T@\"NSString\",C,N"}}
    };
    
    extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_CategoryObject;
    
    static struct _category_t _OBJC_$_CATEGORY_CategoryObject_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "CategoryObject",
        0, // &OBJC_CLASS_$_CategoryObject,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryObject_$_MyAddition,
        0,
        0,
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_CategoryObject_$_MyAddition,
    };
    static void OBJC_CATEGORY_SETUP_$_CategoryObject_$_MyAddition(void ) {
        _OBJC_$_CATEGORY_CategoryObject_$_MyAddition.cls = &OBJC_CLASS_$_CategoryObject;
    }
    
    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[3];
    } _OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryObject_$_MyAddition2 __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        3,
        {{(struct objc_selector *)"printAge", "v16@0:8", (void *)_I_CategoryObject_MyAddition2_printAge},
        {(struct objc_selector *)"printAddress", "v16@0:8", (void *)_I_CategoryObject_MyAddition2_printAddress},
        {(struct objc_selector *)"printName", "v16@0:8", (void *)_I_CategoryObject_MyAddition2_printName}}
    };
    
    static struct /*_prop_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count_of_properties;
        struct _prop_t prop_list[3];
    } _OBJC_$_PROP_LIST_CategoryObject_$_MyAddition2 __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        3,
        {{"name","T@\"NSString\",C,N"},
        {"age","T@\"NSString\",C,N"},
        {"address","T@\"NSString\",C,N"}}
    };
    
    extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_CategoryObject;
    
    static struct _category_t _OBJC_$_CATEGORY_CategoryObject_$_MyAddition2 __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "CategoryObject",
        0, // &OBJC_CLASS_$_CategoryObject,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryObject_$_MyAddition2,
        0,
        0,
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_CategoryObject_$_MyAddition2,
    };
    static void OBJC_CATEGORY_SETUP_$_CategoryObject_$_MyAddition2(void ) {
        _OBJC_$_CATEGORY_CategoryObject_$_MyAddition2.cls = &OBJC_CLASS_$_CategoryObject;
    }
    #pragma section(".objc_inithooks$B", long, read, write)
    __declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
        (void *)&OBJC_CATEGORY_SETUP_$_CategoryObject_$_MyAddition,
        (void *)&OBJC_CATEGORY_SETUP_$_CategoryObject_$_MyAddition2,
    };
    static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= {
        &OBJC_CLASS_$_CategoryObject,
    };
    static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [2] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
        &_OBJC_$_CATEGORY_CategoryObject_$_MyAddition,
        &_OBJC_$_CATEGORY_CategoryObject_$_MyAddition2,
    };
    static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
    
    

    一点点看

    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_CategoryObject_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        2,
        {{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_CategoryObject_MyAddition_printName},
        {(struct objc_selector *)"printValue", "v16@0:8", (void *)_I_CategoryObject_MyAddition_printValue}}
    };
    
    

    这里生成一个静态的struct 名字叫OBJC$CATEGORY_INSTANCE_METHODS_CategoryObject$_MyAddition,并且初始化该结构体;
    这个结构体有三个变量

    entsize 代表的是一个 struct _objc_method 的大小
    method_count 代表category中有几个方法
    method_list[2];是个数组,装的方法名字。

    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_CategoryObject_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        2,
        {{"name","T@\"NSString\",C,N"},
        {"value","T@\"NSString\",C,N"}}
    };
    
    

    属性也是和method 一样的生成方式
    这个结构体有三个变量

    entsize 代表的是一个 struct _prop_t 的大小
    count_of_properties 代表category中有几个属性
    prop_list[2];是个数组,装的属性

    static struct _category_t _OBJC_$_CATEGORY_CategoryObject_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "CategoryObject",
        0, // &OBJC_CLASS_$_CategoryObject,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryObject_$_MyAddition,
        0,
        0,
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_CategoryObject_$_MyAddition,
    };
    

    这里就是给_category_t 结构体赋值,结构体名字规则是 文件头 + 类名+ 类别名。不过这里的 classMethods 和 protocols 都是0 ,因为我们没有给类别增加这些东西。所以都是0.

    static void OBJC_CATEGORY_SETUP_$_CategoryObject_$_MyAddition(void ) {
        _OBJC_$_CATEGORY_CategoryObject_$_MyAddition.cls = &OBJC_CLASS_$_CategoryObject;
    }
    

    我们看category的 cls变量没有赋值。这里给出一个单独的函数对cls进行赋值。

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

    这里保存一个_category_t 数组。
    大概上面的代码看完了

    编译器做了啥事情呢?
    1)、首先编译器生成了实例方法列表
    2)、其次,编译器生成了category本身
    3)、最后,编译器在DATA段下的objc_catlist section里保存了一个大小为1的category_t的数组L_OBJC_LABELCATEGORY$(当然,如果有多个category,会生成对应长度的数组_),用于运行期category的加载。

    追本溯源

    Objective-C的运行是依赖OC的runtime的,而OC的runtime和其他系统库一样,是OS X和iOS通过dyld动态加载的。
    对于OC运行时,入口方法如下(在objc-os.mm文件中):

    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();
        lock_init();
        exception_init();
    
        // Register for unmap first, in case some +load unmaps something
        _dyld_register_func_for_remove_image(&unmap_image);
        dyld_register_image_state_change_handler(dyld_image_state_bound,
                                                 1/*batch*/, &map_images);
        dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
    }
    

    这个函数最终会调用到_read_images 方法中
    怎么知道的呢?我们打断点调试下不就知道了。我们选择symbolic breakpoint 断点,进行调试
    截图如下


    _read_images方法调用

    这个_read_images方法中有有关category相关增加方法。
    我们摘录相关部分

    void _read_images(header_info **hList, uint32_t hCount)
    {
    ···
      // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    ···
        // Fix up remapped classes
        // Class list and nonlazy class list remain unremapped.
        // Class refs and super refs are remapped for message dispatching.
        ···
        // Fix up @selector references
    ···
    // Discover protocols. Fix up protocol refs.
    ···
    // Fix up @protocol references
        // Preoptimized images may have the right 
        // answer already but we don't know for sure.
    ···
       // Realize non-lazy classes (for +load methods and static instances)
    ···
        // Realize newly-resolved future classes, in case CF manipulates them
    
    ···
        // Discover categories. 
        for (EACH_HEADER) {
            category_t **catlist = 
                _getObjc2CategoryList(hi, &count);
            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  
                    /* ||  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");
    
    ···
    // Category discovery MUST BE LAST to avoid potential races 
        // when other threads call the new category code before 
        // this thread finishes its fixups.
    
        // +load handled by prepare_load_methods()
    
    ···
        // Print preoptimization statistics
    
    }
    
    
    

    这个函数的基本结构就是这个样子。分的模块很明确
    我们就看我们category部分

    1 获取category 列表list
    2遍历category list 中的每一个category
    3.获取category 的cls.要是category 没有设置cls 就继续下一个
    4.这里判断cat 是否有实例方法,协议或者属性。有的话就调用下
    addUnattachedCategoryForClass 方法,判断cls 实现的话,就调用remethodizeClass 方法
    5.再判断category 是否有类方法或者协议。有的话也调用addUnattachedCategoryForClass 方法。在检测元类是否实现。调用下remethodizeClass 方法。

    这里有两个关键方法addUnattachedCategoryForClassremethodizeClass

    分别看

    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
        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->count++] = (locstamped_category_t){cat, catHeader};
        NXMapInsert(cats, cls, list);
    }
    

    1.调用unattachedCategories() 函数生成一个NXMapTable * cats。在这里cats 又是全局对象,只有一个

    static NXMapTable *unattachedCategories(void)
    {
        runtimeLock.assertWriting();
        static NXMapTable *category_map = nil;
        if (category_map) return category_map;
        // fixme initial map size
        category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
        return category_map;
    }
    

    这里不看这个对象具体怎么生成的了。

    2.我们从这个单例对象中查找cls ,获取一个category_list *list列表。
    3 要是没有list 指针。那么我们就生成一个category_list 空间。
    4.要是有list 指针,那么就在该指针的基础上再分配出category_list 大小的空间。
    5.在这新分配好的空间,将这个cat 和catHeader 写入。
    6.将数据插入到cats 中。key 是cls 值是list

    数据结构是这样子的

    image.png

    一个全局map ,cls 是key value 是个list (相当于数组)

    接下来看static void remethodizeClass(Class cls) 方法

    static void remethodizeClass(Class cls)
    {
        category_list *cats;
        bool isMeta;
    
        runtimeLock.assertWriting();
    
        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)" : "");
            }
            
            attachCategories(cls, cats, true /*flush caches*/);        
            free(cats);
        }
    }
    

    这个类很简单,就是获取下cats = unattachedCategoriesForClass(cls, false/not realizing/)**
    要是有cats ,那么久调用 attachCategories(cls, cats, true /flush caches/);方法

    看看
    ** static void
    attachCategories(Class cls, category_list cats, bool flush_caches)* 方法

    
    // Attach method lists and properties and protocols from categories to a class.
    // Assumes the categories in cats are all loaded and sorted by load order, 
    // oldest categories first.
    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);
            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);
    }
    

    从这里方法的注释上看,我们知道这个方法是连接方法列表,属性,协议到class。这里category 有个排序问题,谁先加载谁在前面。

    我们看源码分析

    1. 给category 的方法属性和协议分配空间。
    2. 获取方法属性协议 都存放在刚才分配的空间里。
      3 .获取cls 的的bits 指针 class_rw_t。
      4.调用下prepareMethodLists 方法。(没有改变啥东东)
      5.连接方法列表
      6.要是需要刷新缓存,刷新下。
      7.连接属性
      8.连接协议

    这里看6 7 8 的连接。其实method 属性和协议都是继承list_array_tt 类

    class method_array_t : 
        public list_array_tt<method_t, method_list_t> 
    class property_array_t : 
        public list_array_tt<property_t, property_list_t> 
    class protocol_array_t : 
        public list_array_tt<protocol_ref_t, protocol_list_t> 
    

    看看list_array_tt方法的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]));
            }
            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]));
            }
        }
    

    假设这里hasArray中有值。操作如图


    image.png

    大概流程如上图
    这里需要知道

    • 1)、category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA。

    • 2)、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休_,殊不知后面可能还有一样名字的方法。

    • 3)、不单单是method 是这样的逻辑,属性和协议也是一样的。

    旁枝末叶-category和+load方法

    我们知道,在类和category中都可以有+load方法,那么有两个问题:
    1)、在类的+load方法调用的时候,我们可以调用category中声明的方法么?
    2)、这么些个+load方法,调用顺序是咋样的呢?

    我们创建了CategoryLoad方法 和他的两个category1 和category2


    image.png

    分别在这三个.m 文件中实现load 方法

    #import "CategoryLoad.h"
    
    @implementation CategoryLoad
    +(void)load{
        NSLog(@"CategoryLoad");
    }
    @end
    
    #import "CategoryLoad+Category1.h"
    
    @implementation CategoryLoad (Category1)
    +(void)load{
        NSLog(@"CategoryLoad+Category1");
    }
    @end
    
    #import "CategoryLoad+Category2.h"
    
    @implementation CategoryLoad (Category2)
    +(void)load{
        NSLog(@"CategoryLoad+Category2");
    }
    @end
    

    我们在scheme中添加参数


    image.png

    这个时候的编译资源是


    image.png

    顺序是 category2 category category1
    结果是

    objc[39683]: LOAD: class 'CategoryLoad' scheduled for +load
    objc[39683]: LOAD: category 'CategoryLoad(Category2)' scheduled for +load
    objc[39683]: LOAD: category 'CategoryLoad(Category1)' scheduled for +load
    objc[39683]: LOAD: +[CategoryLoad load]
    2018-04-18 17:01:08.938065+0800 CategoryLoadTest[39683:11252165] CategoryLoad
    objc[39683]: LOAD: +[CategoryLoad(Category2) load]
    2018-04-18 17:01:08.939816+0800 CategoryLoadTest[39683:11252165] CategoryLoad+Category2
    objc[39683]: LOAD: +[CategoryLoad(Category1) load]
    2018-04-18 17:01:08.940287+0800 CategoryLoadTest[39683:11252165] CategoryLoad+Category1
    

    所以,对于上面两个问题,答案是很明显的:
    1)、可以调用,因为附加category到类的工作会先于+load方法的执行
    2)、+load的执行顺序是先类,后category,而category的+load执行顺序是根据编译顺序决定的。

    当我们调整下编译顺序


    image.png

    编译结果是

    objc[39907]: LOAD: class 'CategoryLoad' scheduled for +load
    objc[39907]: LOAD: category 'CategoryLoad(Category1)' scheduled for +load
    objc[39907]: LOAD: category 'CategoryLoad(Category2)' scheduled for +load
    objc[39907]: LOAD: +[CategoryLoad load]
    2018-04-18 17:10:24.589178+0800 CategoryLoadTest[39907:11261059] CategoryLoad
    objc[39907]: LOAD: +[CategoryLoad(Category1) load]
    2018-04-18 17:10:24.590856+0800 CategoryLoadTest[39907:11261059] CategoryLoad+Category1
    objc[39907]: LOAD: +[CategoryLoad(Category2) load]
    2018-04-18 17:10:24.591858+0800 CategoryLoadTest[39907:11261059] CategoryLoad+Category2
    

    输出变了
    对于+load的执行顺序是这样,但是对于“覆盖”掉的方法,则会先找到最后一个编译的category里的对应方法。

    方法覆盖找回

    我们知道如何我们用category 把类中的方法给覆盖掉了。那么我们调用这个方法会是category中的方法,那么我们想调用类中的方法怎么办呢?

    从上面的分析我们知道,我们加载的category 方法的时候是把category方法插入到类方法的前面,这样调用方法就调用到category方法。想调用类方法,我们就接着遍历呗。知道跟的那个方法就是类方法了。

     Class  currentClass=  [CategoryObject class];
        CategoryObject *obj = [[CategoryObject alloc]init];
        if (currentClass) {
            unsigned int methodCount;
            Method *methodList = class_copyMethodList(currentClass, &methodCount);
            IMP lastImp = NULL;
            SEL lastSel = NULL;
            for (NSInteger i = 0; i < methodCount; i++) {
                Method method = methodList[i];
                NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method))
                                                          encoding:NSUTF8StringEncoding];
                if ([@"printName" isEqualToString:methodName]) {
                    lastImp = method_getImplementation(method);
                    lastSel = method_getName(method);
                    typedef void (*fn)(id,SEL);
                    if (lastImp != NULL) {
                        fn f = (fn)lastImp;
                        f(obj,lastSel);
                    }
                }
            }
            free(methodList);
        }
    

    这里我把所有的方法都打印出来了
    结果

    2018-04-18 17:25:12.488418+0800 Category[40299:11273773] printName MyAddition2
    2018-04-18 17:25:12.488671+0800 Category[40299:11273773] printName MyAddition
    2018-04-18 17:25:12.488875+0800 Category[40299:11273773] CategoryObject
    

    这个结果说明了最后的一个方法肯定是类的方法。

    源代码地址

    借鉴博客

    相关文章

      网友评论

          本文标题:runtime源码分析之category的实现

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