美文网首页
Category 底层分析

Category 底层分析

作者: CoderGuogt | 来源:发表于2020-10-13 20:52 被阅读0次

Category 主要作用是在不改变原有类的基础上,动态的给已存在的类添加一些方法和属性。

分类(Category)在编译之后的底层结构是 struct category_t,里面存储着当前分类的对象方法、类方法、属性、协议信息,程序在运行的时候,运行时(Runtime)会将分类(Category)中所有的方法、属性、协议数据,合并到类信息中(类对象、元类对象中)

分类编译之后的底层结构

  • 1.1 对当前已存在的类,新建一个分类(此处创建的是一个名为 YXCPerson+Test 的分类),然后使用 clang 将当前创建的分类进行转换
    YXCPerson+Test.h 文件

    // 属性:age 、num 还有一个 不同寻常的 custId 
    // 实例方法(对象方法): test()、eat()
    // 类方法 : sing()
    // 协议 : 遵守了 NSCopying, NSCoding 协议
    @interface YXCPerson (Test)<NSCopying, NSCoding>
    
    @property (nonatomic, assign) int age;
    @property (nonatomic, assign) int num;
    @property (nonatomic, copy, class) NSString *custId;
    
    - (void)test;
    
    - (void)eat;
    
    + (void)sing;
    
    @end
    

    clang 指令

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 分类名称.m
    
  • 1.2 底层结构展示

    struct _category_t {
        const char *name; // 类名
        struct _class_t *cls; // 已存在的类(YXCPerson)
        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; // 属性列表
    };
    
  • 1.3 _class_t 底层结构

    struct _class_t {
        struct _class_t *isa; // isa 指针,实例对象指向类对象,类对象指向元类对象,元类对象指向基类的元类对象(一般是 NSObject)
        struct _class_t *superclass; // 父类
        void *cache; // 缓存
        void *vtable;
        struct _class_ro_t *ro; // 存储的原来类(非分类)的一些方法、协议、属性、成员变量等信息
    };
    
  • 1.4 _class_ro_t 底层结构

    struct _class_ro_t {
        unsigned int flags;
        unsigned int instanceStart;
        unsigned int instanceSize;
        const unsigned char *ivarLayout;
        const char *name;
        const struct _method_list_t *baseMethods;
        const struct _objc_protocol_list *baseProtocols;
        const struct _ivar_list_t *ivars;
        const unsigned char *weakIvarLayout;
        const struct _prop_list_t *properties;
    };
    
  • 1.5 YXCPerson+Test 分类在底层结构为

    static struct _category_t _OBJC_$_CATEGORY_YXCPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "YXCPerson", // 原来的类名
        0, // &OBJC_CLASS_$_YXCPerson,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_YXCPerson_$_Test, // 实例对象方法列表
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_YXCPerson_$_Test, // 类对象方法列表
        (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_YXCPerson_$_Test, // 协议信息列表
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_YXCPerson_$_Test, // 属性列表
    };
    

    可以发现,在编译之后 YXCPerson+Test 这个分类,在底层的结构是一个 _category_t 类型,并且名称为 _OBJC_$_CATEGORY_YXCPerson_$_Test 的结构体。由此可以推测,每个分类在编译之后,都是一个 _category_t 类型,并且命名按照 _OBJC_$_CATEGORY_已存在的类名_$_分类名称 这样的一个方式。

  • 1.6 实例(对象)方法

    _OBJC_$_CATEGORY_INSTANCE_METHODS_YXCPerson_$_Test 这样的一个结构体,顾名思义这是存储着我们当前这个分类的一些实例方法数据,并且为 _method_list_t 类型的结构体

    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_YXCPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method), // 获取到 _objc_method 结构体的所需要的内存空间大小,赋值到 entsize
        2, // method_count 为 2
        {
            {(struct objc_selector *)"test", "v16@0:8", (void *)_I_YXCPerson_Test_test},
            {(struct objc_selector *)"eat", "v16@0:8", (void *)_I_YXCPerson_Test_eat}
        } // 将 test()、eat() 方法放入一个数组,然后赋值给 method_list
    };
    

    struct objc_selector 实际上是一个 SEL

    typedef struct objc_selector *SEL;
    

    _objc_method 结构为

    struct _objc_method {
        struct objc_selector * _cmd; // SEL 地址
        const char *method_type; // 方法签名
        void  *_imp; // 方法实现
    };
    

    通过以上分析,可以总结出:

    1. OC 中实例(对象)方法在底层的实现是一个 _objc_method 类型的结构体,它包含了方法的声明、签名以及实现,编译器会将方法的声明、签名、实现信息放入到这个结构体当中存储起来。

    2. 将一个个的实例(对象)方法通过 _objc_method 结构体存储好后,放入一个 _method_list_t 结构体中的 method_list 数组中(这个数组的个数会根据当前分类的方法个数,分配空间),同时按照 _OBJC_$_CATEGORY_INSTANCE_METHODS_原类名称_$_分类名称 这样的一个格式给这个结构体取名。

    3. 最后将 _method_list_t 类型的赋值给 _category_t 中的 instance_methods,这样就将当前分类中的实例(对象)方法存储到了当前分类结构体中去了。

  • 1.7 类方法

    类方法存储到一个名为 _OBJC_$_CATEGORY_CLASS_METHODS_YXCPerson_$_Test 的结构体,这个结构体也是一个 _method_list_t,跟实例(对象)方法的原理是一致的。

    
    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count; // 类方法个数
        struct _objc_method method_list[1]; // 存放着 _objc_method 类型的结构体数组
    } _OBJC_$_CATEGORY_CLASS_METHODS_YXCPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"sing", "v16@0:8", (void *)_C_YXCPerson_Test_sing}}
    };
    
    
  • 1.8 协议信息

    协议信息存储到了一个名为 _OBJC_CATEGORY_PROTOCOLS_$_YXCPerson_$_Test_protocol_list_t 类型的结构体中,_protocol_list_t 结构体。

    static struct /*_protocol_list_t*/ {
        long protocol_count;  // Note, this is 32/64 bit
        struct _protocol_t *super_protocols[2];
    } _OBJC_CATEGORY_PROTOCOLS_$_YXCPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        2,
        &_OBJC_PROTOCOL_NSCopying,
        &_OBJC_PROTOCOL_NSCoding
    };
    
  • 1.9 属性信息

    属性信息存储到了一个名为 _OBJC_$_PROP_LIST_YXCPerson_$_Test_prop_list_t 类型的结构体,其中这个结构体中有一个 prop_list 属性,里面存放的就是当前分类所有的属性,当然在底层的结构是一个 _prop_t 结构体。

    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_YXCPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        2,
        {{"age","Ti,N"},
        {"num","Ti,N"}}
    };
    
    struct _prop_t {
        const char *name;
        const char *attributes;
    };
    

分类加载处理过程

  1. 通过运行时(Runtime)加载某个类的所有分类数据
  2. 把所有分类的方法、属性、协议数据,合并到一个数组中,后参与编译的分类数据,会在数组的最前面
  3. 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

下面通过源码来查看这个过程,下载最新的源码

  1. 找到 objc-os.mm 文件,并且找到 _objc_init 函数,在 _objc_init 函数中有一个 _dyld_objc_notify_register 函数,这个函数第一个参数传入了一个镜像(map_images

    /***********************************************************************
    * _objc_init
    * Bootstrap initialization. Registers our image notifier with dyld.
    * Called by libSystem BEFORE library initialization time
    **********************************************************************/
    
    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();
        runtime_init();
        exception_init();
        cache_init();
        _imp_implementationWithBlock_init();
    
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    
        #if __OBJC2__
        didCallDyldNotifyRegister = true;
        #endif
    }
    
  2. objc-runtime-new.mm 文件中,找到 map_images 函数,发现返回的结果是通过调用 map_images_nolock 函数得到的结果

    /***********************************************************************
    * map_images
    * Process the given images which are being mapped in by dyld.
    * Calls ABI-agnostic code after taking ABI-specific locks.
    *
    * Locking: write-locks runtimeLock
    **********************************************************************/
    void
    map_images(unsigned count, const char * const paths[],
            const struct mach_header * const mhdrs[])
    {
        mutex_locker_t lock(runtimeLock);
        return map_images_nolock(count, paths, mhdrs);
    }
    
  3. objc-os.mm 文件中找到 map_images_nolock 函数,查看该函数

    ...
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    
  4. 跳转到 _read_images 函数中查看,位于 objc-runtime-new.mm

    // Discover categories. Only do this after the initial category
    // attachment has been done. For categories present at startup,
    // discovery is deferred until the first load_images call after
    // the call to _dyld_objc_notify_register completes. rdar://problem/53119145
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            // 加载分类信息
            load_categories_nolock(hi);
        }
    }
    
  5. 跳转到 load_categories_nolock 函数中查看

    ...
    if (cat->instanceMethods ||  
        cat->protocols ||  
        cat->instanceProperties)
        {
            if (cls->isRealized()) {
                // 拼接分类信息
                attachCategories(cls, &lc, 1, ATTACH_EXISTING);
            } else {
                objc::unattachedCategories.addForClass(lc, cls);
            }
        }
    ...
    
  6. 跳转到 attachCategories 这个函数中查看

    ...
    {
        auto& entry = cats_list[i];
    
        // 方法列表
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
    
        // 属性列表
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }
    
        // 协议列表
        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }
    ...
    

相关文章

网友评论

      本文标题:Category 底层分析

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