美文网首页
iOS の Category

iOS の Category

作者: 游城十代2dai | 来源:发表于2020-04-08 16:12 被阅读0次

    0x00 Category 实现原理

    编译之后每个 Category 文件都会生成一个 struct _category_t 的结构体

    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;
        };
    

    看下面源码可以知道( while i-- ), Category 是按照编译顺序倒序加入 method_list

    // Category 主要部分源码
    // 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;
        }
    }
    

    所以

    1. 通过 Runtime 加载某各类的所有 Category 数据
    2. 把所有内容(属性方法协议等)放入一个大的数组中(倒序排列)
    3. 合并后的数据插入到原始数据的前面, 所以并不是 Category 内实现的方法覆盖了原方法

    0x01 Category 与 Extension 区别

    • Extension 是在编译的时候数据就和类的信息放在一起
    • Category 是在运行时才将数据合并在一起

    0x02 Category 中的 +load 方法

    1. Category 的 load 方法什么时候调用?
    • load 方法是在运行时加载类和分类的时候调用, 父类会优先调用
    1. 调用顺序 (看下面的源码)
    • 先调用类的 load 方法, 先按照编译顺序, 然后按照先父类, 后子类
    • 再调用Category 的 load 方法, 按照编译顺序
    1. 为什么 load 方法会被全部调用, 而其他的方法只会调用最前面的?
      通过源码可以知道因为 load 方法是系统通过函数地址调用, 所有都调了一遍, 而其他方法是手动通过消息发送机制调用, 故只调用了最前面的, 手动 load 也是消息机制也只会调用最前面的 load
    /*  prepare_load_methods 这里会准备好执行 load 方法的类的顺序  */
     
     void
     load_images(const char *path __unused, const struct mach_header *mh)
     {
         // Return without taking locks if there are no +load methods here.
         if (!hasLoadMethods((const headerType *)mh)) return;
    
         recursive_mutex_locker_t lock(loadMethodLock);
    
         // Discover load methods
         {
             rwlock_writer_t lock2(runtimeLock);
             prepare_load_methods((const headerType *)mh);
         }
    
         // Call +load methods (without runtimeLock - re-entrant)
         call_load_methods();
     }
    
    /*  递归将类和父类添加入数组, 优先加入的是父类, 而选择类的顺序为编译顺序(即调整编译顺序 load 调用也会改变)  */
     
     static void schedule_class_load(Class cls)
     {
         if (!cls) return;
         assert(cls->isRealized());  // _read_images should realize
    
         if (cls->data()->flags & RW_LOADED) return;
    
         // Ensure superclass-first ordering
         schedule_class_load(cls->superclass);
    
         add_class_to_loadable_list(cls);
         cls->setInfo(RW_LOADED);
     }
    
    
    /*  while (loadable_classes_used > 0) { call_class_loads(); }
     *  上面的 while 就是说存在可用的类时就会调用类的 load 方法 (call_class_loads), 没有可用的类的时候就会调用类别的方法(call_category_loads)  */
     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;
     }
    
    

    0x03 About Initialize

    1. 类第一次接收到消息的时候调用
    2. 调用子类的, 会先调用父类的
    3. 如果子类没有实现, 就会调用父类的, 所以会出现多次调用
    4. 如果 Category 实现了, 就会调用 Category 的
    // 递归去做父类的初始化
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    } 
        
    // 实际就是消息发送
    void callInitialize(Class cls)
    {
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
        asm("");
    }
    

    0x04 为 Category 添加属性

    Category 不能添加成员变量, 但是可以间接实现添加属性

    主要是用 Runtime 的对象关联手段, Runtime 中有个 AssociationsManager 类做管理, 其中有全局的 hashmap 负责收藏属性的值

    这个 hashmap 是两层的就比如下面的例子在实际的 hashmap 中是类似这样的{ &per : { name 的 key : name 的 value } }


    0x05 我的测试代码

    使用 Command Line Tool 创建测试就好

    #import <Foundation/Foundation.h>
    #import <objc/runtime.h>
    
    void _aboutCategory(void);
    void _about_load(void);
    void _about_initialize(void);
    void _about_instance_ivar(void);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
    //        _aboutCategory();
    //        _about_load();
    //        _about_initialize();
            _about_instance_ivar();
        }
        return 0;
    }
    
    void printMethodNamesOfClass(Class cls) {
        // 定义
        unsigned int outCount = 0;
        Method *methodList = NULL;
        Method tempMethod = NULL;
        NSMutableString *strResult = [NSMutableString string];
        NSString *strTemp = [NSString string];
        
        // copy 出方法列表
        methodList = class_copyMethodList(cls, &outCount);
        
        // 遍历
        for (int i = 0; i < outCount; ++i) {
            tempMethod = methodList[i];
            strTemp = NSStringFromSelector(method_getName(tempMethod));
            [strResult appendFormat:@"\n%@", strTemp];
        }
        
        NSLog(@"%@", strResult);
    }
    
    
    @interface Person : NSObject
    - (void)walk;
    @end
    @implementation Person
    + (void)initialize {
        NSLog(@"+initialize Person");
    }
    + (void)load {
        NSLog(@"+load Person");
    }
    - (void)walk {
        NSLog(@"any person can walk");
    }
    @end
    
    @interface Person (Run)
    - (void)run;
    @end
    @implementation Person (Run)
    - (void)run {
        NSLog(@"any person can run");
    }
    @end
    
    @interface Person (Eat)
    - (void)eat;
    @end
    @implementation Person (Eat)
    - (void)eat {
        NSLog(@"any person can eat");
    }
    @end
    
    /**
     * Category 与 Extension 区别在于:
     * Extension 是在编译的时候数据就和类的信息放在一起
     * Category 是在运行时才将数据合并在一起
     */
    
    void _aboutCategory() {
        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;
        };
        /**
         * 编译之后每个 Category 文件都会生成一个 struct _category_t 的结构体
         * 看下面源码可以知道( while i-- ), Category 是按照编译顺序倒序加入 method_list 内
         * 实现原理如下:
         * 1. 通过 Runtime 加载某各类的所有 Category 数据
         * 2. 把所有内容(属性方法协议等)放入一个大的数组中(倒序排列)
         * 3. 合并后的数据插入到原始数据的前面, 所以并不是 Category 内实现的方法覆盖了原方法
         */
        
        Person *person = Person.alloc.init;
        
        [person walk];
        [person eat];
        [person run];
        
        printMethodNamesOfClass(person.class);
        
    }
    
    // Category 主要部分源码
    // 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;
    //    }
    //}
    
    @interface Student : Person
    + (void)test;
    @end
    @implementation Student
    //+ (void)initialize {
    //    NSLog(@"+initialize Student");
    //}
    + (void)load {
        NSLog(@"+load Student");
    }
    + (void)test {
        NSLog(@"+test");
    }
    @end
    
    @interface Student (Run)
    
    @end
    @implementation Student (Run)
    //+ (void)initialize {
    //    NSLog(@"+initialize Run");
    //}
    + (void)load {
        NSLog(@"+load Run");
    }
    + (void)test {
        NSLog(@"+test Run");
    }
    @end
    
    @interface Student (Eat)
    
    @end
    @implementation Student (Eat)
    //+ (void)initialize {
    //    NSLog(@"+initialize Eat");
    //}
    + (void)load {
        NSLog(@"+load Eat");
    }
    + (void)test {
        NSLog(@"+test Eat");
    }
    @end
    
    /** 下面注释中的是相关部分源码
     * 1. Category 的 load 方法什么时候调用?
     * load 方法是在运行时加载类和分类的时候调用, 父类会优先调用
     *
     * 2. 调用顺序 (看下面的源码)
     * 先调用类的 load 方法, 先按照编译顺序, 然后按照先父类, 后子类
     * 再调用Category 的 load 方法, 按照编译顺序
     *
     * 3. 为什么 load 方法会被全部调用, 而其他的方法只会调用最前面的?
     * 通过源码可以知道因为 load 方法是系统通过函数地址调用, 所有都调了一遍, 而其他方法是手动通过消息发送机制调用, 故只调用了最前面的, 手动 load 也是消息机制也只会调用最前面的 load
     */
    void _about_load() {
        [Student test];
        Student *std = Student.alloc.init;
        printMethodNamesOfClass(object_getClass(std.class));
    }
    
    /*  prepare_load_methods 这里会准备好执行 load 方法的类的顺序
     
     void
     load_images(const char *path __unused, const struct mach_header *mh)
     {
         // Return without taking locks if there are no +load methods here.
         if (!hasLoadMethods((const headerType *)mh)) return;
    
         recursive_mutex_locker_t lock(loadMethodLock);
    
         // Discover load methods
         {
             rwlock_writer_t lock2(runtimeLock);
             prepare_load_methods((const headerType *)mh);
         }
    
         // Call +load methods (without runtimeLock - re-entrant)
         call_load_methods();
     }
     */
    
    /*  递归将类和父类添加入数组, 优先加入的是父类, 而选择类的顺序为编译顺序(即调整编译顺序 load 调用也会改变)
     
     static void schedule_class_load(Class cls)
     {
         if (!cls) return;
         assert(cls->isRealized());  // _read_images should realize
    
         if (cls->data()->flags & RW_LOADED) return;
    
         // Ensure superclass-first ordering
         schedule_class_load(cls->superclass);
    
         add_class_to_loadable_list(cls);
         cls->setInfo(RW_LOADED);
     }
     
     */
    
    /*  while (loadable_classes_used > 0) { call_class_loads(); }
     *  上面的 while 就是说存在可用的类时就会调用类的 load 方法 (call_class_loads), 没有可用的类的时候就会调用类别的方法(call_category_loads   )
     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;
     }
    
     */
    
    
    /** initialize
     * 1. 类第一次接收到消息的时候调用
     * 2. 调用子类的, 会先调用父类的
     * 3. 如果子类没有实现, 就会调用父类的, 所以会出现多次调用
     * 4. 如果 Category 实现了, 就会调用 Category 的
     */
    void _about_initialize(void) {
    //    [Student alloc]  不调用就不执行
        [Student alloc];
    }
    
    
    @interface Person (Name)
    //{ NSString * _name }  成员变量无法添加的
    
    @property (nonatomic, copy) NSString *name;
    @end
    
    @implementation Person (Name)
    
    - (void)setName:(NSString *)name {
        objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSString *)name {
        return objc_getAssociatedObject(self, _cmd);
    }
    
    @end
    
    /**
     * Category 不能添加成员变量, 但是可以间接实现添加属性
     * 主要是用 Runtime 的对象关联手段, Runtime 中有个 AssociationsManager 类做管理, 其中有全局的 hashmap 负责收藏属性的值
     * 这个 hashmap 是两层的就比如下面的例子在实际的 hashmap 中是类似这样的 {  &per : { name 的 key :  name 的 value } }
     */
    void _about_instance_ivar(void) {
        Person *per = Person.alloc.init;
        
        per.name = @"lily";
        NSLog(@"%@", per.name);
        
        per.name = @"lily_2";
        NSLog(@"%@", per.name);
    }
    
    

    相关文章

      网友评论

          本文标题:iOS の Category

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