美文网首页
iOS 分类 category(二)源码实现

iOS 分类 category(二)源码实现

作者: 萨缪 | 来源:发表于2020-04-27 22:35 被阅读0次

    category简介

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

    可以把类的实现分开在几个不同的文件里面。这样做有几个显而易见的好处,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_inithooksB")) 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 名字叫OBJCCATEGORYINSTANCEMETHODSCategoryObjectCATEGORYINSTANCEMETHODSCategoryObject_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的加载。比如上面这个例子 大小就为2
    追本溯源

    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();
    //静态初始化 运行C++静态构造函数
    static_init();
    lock_init();
    //异常初始化 初始化libobjc的异常处理系统 由map_images()调用
    exception_init();
        
    // Register for unmap first, in case some +load unmaps something
    //    先注册取消映射,以防某些 +load 方法取消映射某些内容
    //那么 +load方法什么情况下会被调用呢?
    //load函数是只要你动态加载或者静态引用了这个类,那么load就会被执行,它并不需要你显示的去创建一个类后才会执行,同时只执行一次。
    //另外就是关于load的执行顺序问题,所有的superclass的load执行完以后才会执行该类的load,以及class中的load方法是先于category中的load执行的。
    _dyld_register_func_for_remove_image(&unmap_image);
    dyld_register_image_state_change_handler(dyld_image_state_bound,
                                             1/*batch*/, &map_2_images);
    

    dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/not batch/, &load_images);
    }

    这个函数最终会调用到_read_images 方法中
    那么 _read_images方法又是什么呢?
    这个方法作用就是读取各个 section 中的数据并放到缓存中,这里的缓存大部分都是全局静态变量,载体就是我们一个c++map类的 hashmap

    想深入了解看这个
    怎么知道的呢?我们打断点调试下不就知道了。我们选择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) {
    //1.获取category列表list
    category_t **catlist =
    _getObjc2CategoryList(hi, &count);
    //2.遍历category list 中的每一个category
    for (i = 0; i < count; i++) {
    category_t *cat = catlist[i];
    // 3.获取category 的cls.要是category 没有设置cls 就继续下一个
    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;
            //4.这里判断cat 是否有实例方法,协议或者属性。有的话就调用下
    

    addUnattachedCategoryForClass 方法,判断cls 实现的话,就调用remethodizeClass 方法
    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" : "");
    }
    }

            //5.再判断category 是否有类方法或者协议。有的话也调用addUnattachedCategoryForClass 方法。在检测元类是否实现。调用下remethodizeClass 方法。
            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

    }

    这个函数的基本结构就是这个样子。分的模块很明确
    这里有两个关键方法addUnattachedCategoryForClass 和remethodizeClass 。

    分别看
    作用:为类添加未附加的类别 并通过NXMapInsert函数,更新所属类的Category列表

    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
    //1.调用unattachedCategories() 函数生成一个NXMapTable * cats,获取到未添加的Category哈希表 在这里cats 是全局对象,只有一个
    //NXMapTable这个结构体的作用是:他是一个map类(哈希表)的变量,而map类允许散列任意关联[key->value]。键和值必须是指针或整数,客户端负责分配/解除分配此数据。提供了释放回调。
    //作为性能良好的可伸缩数据结构,哈希表在开始变满时的大小将增加一倍,从而保证了平均恒定时间访问和线性大小
    NXMapTable *cats = unattachedCategories();
    //这个category_list 是用来存储所有的category的
    category_list *list;
    
    //2.我们从这个单例对象中查找cls ,获取一个category_list *list列表。
    //cls:要扩展的类对象
    list = (category_list *)NXMapGet(cats, cls);
    
    //3 要是没有list 指针。那么我们就生成一个category_list 空间。
    if (!list) {
        list = (category_list *)
            calloc(sizeof(*list) + sizeof(list->list[0]), 1);
    } else {
    // 4.要是有list 指针,那么就在该指针的基础上再分配出category_list 大小的空间。
        list = (category_list *)
            realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
    }
    // 5.在这新分配好的空间,将这个cat 和catHeader 写入。
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
    // 6.将数据插入到cats 中。key 是cls(要扩展的类对象) 值是list
    NXMapInsert(cats, cls, list);
    

    }

    下面是unattachedCategories()实现方法

    // 获取未添加到Class中的category哈希表
    static NXMapTable *unattachedCategories(void)
    {
    // 未添加到Class中的category哈希表
    runtimeLock.assertWriting();
    static NXMapTable *category_map = nil;
    if (category_map) return category_map;
    // fixme initial map size
    //固定哈希表的初始内存大小 创建存储category的哈希表
    category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
    return category_map;
    }

    这个哈希表的数据结构是这样子的:

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

    上面只是完成了向Category哈希表中添加的操作,这时候哈希表中存储了所有category_t对象。然后需要调用remethodizeClass函数,向对应的Class中添加Category的信息。

    在remethodizeClass函数中会查找传入的Class参数对应的Category数组,然后将数组传给attachCategories函数,执行具体的添加操作。

    // 将Category的信息添加到Class,包含method、property、protocol
    static void remethodizeClass(Class cls)
    {
    category_list *cats;
    bool isMeta;
    isMeta = cls->isMetaClass();

    // 从Category哈希表中查找category_t对象,并将已找到的对象从哈希表中删除
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        attachCategories(cls, cats, true /*flush caches*/);
        free(cats);
    }
    

    }

    在attachCategories函数中,查找到Category的方法列表、属性列表、协议列表,然后通过对应的attachLists函数,添加到Class对应的class_rw_t结构体中。这个class_rw_t结构体就是存储类相关信息的一个结构体

    // 获取到Category的Protocol list、Property list、Method list,然后通过attachLists函数添加到所属的类中
    static void attachCategories(Class cls, category_list *cats, bool flush_caches)
    {
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();
    
    // 按照Category个数,分配对应的内存空间
    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));
    
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    
    // 循环查找出Protocol list、Property list、Method list
    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);
    

    }

    这个过程就是将Category中的信息,添加到对应的Class中,一个类的Category可能不只有一个,在这个过程中会将所有Category的信息都合并到Class中。
    方法覆盖找回

    在有多个Category和原类的方法重复定义的时候,原类和所有Category的方法都会存在,并不会被后面的覆盖。假设有一个方法叫做method,Category和原类的方法都会被添加到方法列表中,只是存在的顺序不同。

    在进行方法调用的时候,会优先遍历Category的方法,并且后面被添加到项目里的Category,会被优先调用。上面的例子调用顺序就是Category3 -> Category2 -> Category1 -> TestObject。如果从方法列表中找到方法后,就不会继续向后查找,这就是类方法被Category”覆盖”的原因。
    两个问题

    在有多个Category和原类方法重名的情况下,怎样在一个Category的方法被调用后,调用所有Category和原类的方法?
    

    可以在一个Category方法被调用后,遍历方法列表并调用其他同名方法。但是需要注意一点是,遍历过程中不能再调用自己的方法,否则会导致递归调用。为了避免这个问题,可以在调用前判断被调动的方法IMP是否当前方法的IMP。

    那怎样在任何一个Category的方法被调用后,只调用原类方法呢?
    

    根据上面对方法调用的分析,Runtime在调用方法时会优先所有Category调用,所以可以倒叙遍历方法列表,只遍历第一个方法即可,这个方法就是原类的方法。

    分类源码

    相关文章

      网友评论

          本文标题:iOS 分类 category(二)源码实现

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