美文网首页iOS开发记录iOS学习开发
Objective-C中Category的本质

Objective-C中Category的本质

作者: DinoGuy | 来源:发表于2018-05-24 04:00 被阅读6次

    写一个 Person 的分类:Person+DO

    Person+DO.h 文件:

    #import "Person.h"
    
    @interface Person (DO) <NSCopying>
    
    @property (nonatomic, assign) int number;
    
    - (void)testInstanceMethod;
    
    + (void)testClassMethod;
    
    @end
    

    Person_DO.m 文件:

    #import "Person+DO.h"
    
    @implementation Person (DO)
    
    - (void)testInstanceMethod{}
    
    + (void)testClassMethod{}
    
    @end
    

    在终端输入以下命令:

    $ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+DO.m -o Person+DO-arm64.cpp
    

    打开 Person+DO-arm64.cpp 该文件,我们想要的都在这里面。

    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;    // 属性列表
    };
    
    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_Person_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"testInstanceMethod", "v16@0:8", (void *)_I_Person_DO_testInstanceMethod}}
    };
    

    _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_DO :从名字(INSTANCE_METHODS)可看出是对象方法列表结构体
    testInstanceMethod:正是我们分类中定义的对象方法

    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_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"testClassMethod", "v16@0:8", (void *)_C_Person_DO_testClassMethod}}
    };
    

    _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_DO :从名字(CLASS_METHODS)可看出是类方法列表结构体
    testClassMethod:正是我们分类中定义的类方法

    static const char *_OBJC_PROTOCOL_METHOD_TYPES_NSCopying [] __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "@24@0:8^{_NSZone=}16"
    };
    
    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_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        1,
        &_OBJC_PROTOCOL_NSCopying
    };
    

    可看出,协议方法先通过 _method_list_t 结构体存储,之后通过 _protocol_t 结构体存储在 _OBJC_CATEGORY_PROTOCOLS_$_Person_$_DO 中和 _protocol_list_t 结构体一一对应。

    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_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        1,
        {{"number","Ti,N"}}
    };
    

    从属性列表结构体中,发现了我们自己写的 number 属性

    extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_Person;
    
    static struct _category_t _OBJC_$_CATEGORY_Person_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "Person",
        0, // &OBJC_CLASS_$_Person,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_DO,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_DO,
        (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_DO,
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_DO,
    };
    static void OBJC_CATEGORY_SETUP_$_Person_$_DO(void ) {
        _OBJC_$_CATEGORY_Person_$_DO.cls = &OBJC_CLASS_$_Person;
    }
    

    struct _class_t OBJC_CLASS_$_Person_class_t 类型的 OBJC_CLASS_$_Person 结构体
    _OBJC_$_CATEGORY_Person_$_DO.cls = &OBJC_CLASS_$_Person;cls 指针指向分类的主类类对象 Person 的地址

    至此,通过以上分类的源码,我们在分类中定义的 对象方法类方法属性协议 等都存放在 catagory_t 结构体中。

    总结:

    1. Category的实现原理:将分类中的方法,属性,协议数据放在 category_t 结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。
    2. Category中不能加属性:Category可以添加属性,但是并不会自动生成成员变量及 set/get 方法。因为 category_t结构体中并不存在成员变量。
      进一步分析:
      成员变量是存放在实例对象中的,并且编译使就已经决定好了。而分类是在运行时才去加载的,无法在程序运行时将分类的成员变量添加到实例对象的结构体中。因此分类中不可以添加成员变量。

    load 和 initialize

    objc-loadmethod.mm 文件中:

    void call_load_methods(void)
    {
        static bool loading = NO;
        bool more_categories;
    
        loadMethodLock.assertLocked();
    
        // Re-entrant calls do nothing; the outermost call will finish the job.
        if (loading) return;
        loading = YES;
    
        void *pool = objc_autoreleasePoolPush();
    
        do {
            // 1. Repeatedly call class +loads until there aren't any more
            while (loadable_classes_used > 0) {
                call_class_loads();
            }
    
            // 2. Call category +loads ONCE
            more_categories = call_category_loads();
    
            // 3. Run more +loads if there are classes OR more untried categories
        } while (loadable_classes_used > 0  ||  more_categories);
    
        objc_autoreleasePoolPop(pool);
    
        loading = NO;
    }
    

    优先调用类的 load 方法,之后调用分类的 load 方法

    void callInitialize(Class cls)
    {
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
        asm("");
    }
    

    initialize 特点:

    1. 第一次使用类的时候会调用(第一次接收到消息)
    2. 调用子类的 initialize 之前,会先保证调用父类的 initialize 方法
    3. 如果之前已经调用过 initialize,就不会再调用 initialize 方法
    4. 当分类重写 initialize 方法时会先调用分类的方法
    static void call_class_loads(void)
    {
        int i;
        
        // Detach current loadable list.
        struct loadable_class *classes = loadable_classes;
        int used = loadable_classes_used;
        loadable_classes = nil;
        loadable_classes_allocated = 0;
        loadable_classes_used = 0;
        
        // Call all +loads for the detached list.
        for (i = 0; i < used; i++) {
            Class cls = classes[i].cls;
            load_method_t load_method = (load_method_t)classes[i].method;
            if (!cls) continue; 
    
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
            }
            (*load_method)(cls, SEL_load);
        }
        
        // Destroy the detached list.
        if (classes) free(classes);
    }
    
    // Call all +loads for the detached list.
        for (i = 0; i < used; i++) {
            Category cat = cats[i].cat;
            load_method_t load_method = (load_method_t)cats[i].method;
            Class cls;
            if (!cat) continue;
    
            cls = _category_getClass(cat);
            if (cls  &&  cls->isLoadable()) {
                if (PrintLoading) {
                    _objc_inform("LOAD: +[%s(%s) load]\n", 
                                 cls->nameForLogging(), 
                                 _category_getName(cat));
                }
                (*load_method)(cls, SEL_load);
                cats[i].cat = nil;
            }
        }
    

    load 方法通过直接拿到 load 方法地址进行调用

    总结:

    loadinitialize 区别:

    1. load 是根据函数地址直接调用
      initialize 是通过 objc_msgSend 调用
    2. loadruntime 加载类、分类的时候调用(只会调用1次)
      initialize 是类第一次接收到消息的时候调用,每一个类只会 initialize 一次(父类的 initialize 方法可能会被调用多次,子类未实现)
    3. 分类中 load 方法不会覆盖本类的 load 方法
      如果分类实现了 initialize 方法,就覆盖类本身的 initialize 方法

    相关文章

      网友评论

        本文标题:Objective-C中Category的本质

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