• 01-03 category 原理概述


    1、Category

    1.1、原理

    #import "FQPeople+Test.h"
    @implementation FQPeople (Test)
    + (void)test{
        
    }
    - (void)test1{
        
    }
    @end
    
    • 分类的对象方法test1存放在类对象FQPeople中

    • 分类的类方法test存在FQPeople元类中

    使用:

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc FQPeople+Test.m
    

    在编译时生成c++代码

    每一个分类都会产生一个结构体

    XFPeople XFPeople+Category1 XFPeople+Category2

    产生两个结构体

    struct _category_t {
    	const char *name;//类名FQPeople
    	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_t中

    在运行时通过runtime动态的将分类的方法合并到类对象、元类对象中 在程序启动的时候就合并

    而不是在使用该分类 或者编译时调用的时候才合并

    通过这个流程得知 依次插入

    XFPeople XFPeople+Category1 XFPeople+Category2

    着三个中都有-(void)test{}方法,会先从分类中找方法,如果分类中没有再从对象中找方法 (数组遍历从索引0开始的)

    源码:

    至于是先执行 XFPeople+Category1 或者是 XFPeople+Category2中的方法,则存在不确定性,这个要看哪个先编译,最后编译的放在最前面

    总结整体流程:

    1、通过runtime加载某一个类的所有分类数据

    2、把所有的category的方法属性协议合并到一个数组中(后编译的分类数据会放在数组的前面)

    3、将合并后的分类数据(方法,属性,协议)插入到类原来数据的前面

    最后粘贴一下运行时源码
    最后粘贴一下运行时源码

    /*
     类对象,分类列表
     cls XQPeople
     cats = [category_t(Test),category_t(Eat)]
     */
    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-t,method-t],[method-t,method-t]
         ]
         */
        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];
    ///取出分类中的对象方法(或者元类方法)  最终把所有的分类方法列表放入mlists大数组中
            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);
    }
    
    
    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;
                //array()->lists 原来的方法列表
                memmove(array()->lists + addedCount, array()->lists, 
                        oldCount * sizeof(array()->lists[0]));
                ///addedLists 所有的分类方法列表
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));
            }
            else if (!list  &&  addedCount == 1) {
                // 0 lists -> 1 list
                list = addedLists[0];
            } 
            ...
    

    1.2、扩展(类扩展)

    @interface FQPeople()
    
    @end
    @implementation FQPeople
    
    @end
    

    类扩展是在编译的时候就把扩展的信息合并到类里面去,而分类是在运行时合并的

    2、Category实现原理(m)

    • 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;
    };
    
    • 运行时(程序运行或者启动的时候)会将分类的数据合并到原来的类信息中(类对象、元类对象)

    3、Category 和class extension 的区别(m)

    • class extension是在编译的时候就把扩展的信息合并到类里面去,

    • Category是在运行时将数据合并到类信息中

    4、load initialize

    4.1、load的原理

    • load方法在在runtime加载类、分类的时候调用

    相当于运行时程序一旦启动的时候进行调用,加载类的时候调用类的load 加载分类的时候调用分类的load,

    • 每个类、分类的+load,在程序运行过程中只调用一次;

    4.1.1、不存在继承关系的load

    证明

    @implementation People (Test1)
    + (void)load{
        NSLog(@"%s",__func__);
    }
    + (void)test{
        NSLog(@"%s",__func__);
    }
    @end
    
    @implementation People (Test2)
    + (void)load{
        NSLog(@"%s",__func__);
    }
    + (void)test{
        NSLog(@"%s",__func__);
    }
    @end
    
    @implementation People
    + (void)load{
        NSLog(@"%s",__func__);
    }
    + (void)test{
        NSLog(@"%s",__func__);
    }
    

    直接运行

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
        }
        return 0;
    }
    
    
    +[People load]
    +[People(Test2) load]
    +[People(Test1) load]
    

    可以看出 只要有load方法在程序启动的时候就会调用

    void  printMethodNamesOfCalss(Class cls){
        unsigned int count;
        Method *methodList = class_copyMethodList(cls, &count);
        NSMutableString *methodsNames = [NSMutableString string];
        for (int i = 0; i<count; i++) {
            Method method = methodList[i];
            NSString *methodsName = NSStringFromSelector(method_getName(method));
            [methodsNames appendString:methodsName];
            [methodsNames appendString:@", "];
        }
        free(methodList);
        NSLog(@"%@   %@",cls,methodsNames);
        
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
          printMethodNamesOfCalss(object_getClass([People class]));
          [People test];
        }
        return 0;
    }
    
     程序启动加载时(runtime)调用load
     +[People load]
     +[People(Test2) load]
     +[People(Test1) load]
     ///打印出对象和分类对象的方法列表  可以看得有多个load 多个test
     People        load, test,       load, test,         load, test,
    
     ///[People test]; 只调用了Test2的test方法
     +[People(Test2) test]
    

    调用顺序

    • 类的load方法

    疑问:同样是类方法,且都在类方法列表中存在 为什么load调用了三次 而test只调用了分类中的呢?

    void call_load_methods(void){
        do {
            //先调用类的load方法
            // 1. Repeatedly call class +loads until there aren't any more
            while (loadable_classes_used > 0) {
                call_class_loads();
            }
            // 2. Call category +loads ONCE 再调动分类的load方法
            more_categories = call_category_loads();
            // 3. Run more +loads if there are classes OR more untried categories
        } while (loadable_classes_used > 0  ||  more_categories);
    
      
    
    static void call_class_loads(void)
    {
        int i;
        
        // Detach current loadable list. 所有有load的类的列表
        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.遍历load类列表
        for (i = 0; i < used; i++) {
            Class cls = classes[i].cls;
    //        取出该load的load类方法指针
            load_method_t load_method = (load_method_t)classes[i].method;
            if (!cls) continue; 
    
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s load]
    ", cls->nameForLogging());
            }
            ///直接调用该load方法
            (*load_method)(cls, SEL_load);
        }
        // Destroy the detached list.
        if (classes) free(classes);
    }
    
    typedef void(*load_method_t)(id, SEL);
    
    struct loadable_class {
        Class cls;  // may be nil
        IMP method;//这个里面存放的就是类load方法
    };
    
    struct loadable_category {
        Category cat;  // may be nil
        IMP method;//这个里面存放的就是分类load方法
    };
    
    

    答:为什么load会执行三次

    因为在运行时时期 会遍历所有带有load方法的类,直接拿到load方法的地址 直接调用

    而+(void)test{}呢?

    这个方法是通过发送消息

    objc_msgSend(objc_getClass("People"), sel_registerName("test"));
    

    4.1.2、存在继承关系的load

    • 先调用类load方法 1000个类的话,那先调用那个类呢?

      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  递归调用 先调用s父类
          schedule_class_load(cls->superclass);
      
          ///把父类加入到包含load类的列表中 列表中存储类对象
          add_class_to_loadable_list(cls);
          cls->setInfo(RW_LOADED); 
      }
      
      

      首先会先调用父类的load方法 在调用子类的load方法

      如果不存在继承关系呢?

    void prepare_load_methods(const headerType *mhdr)
    {
        size_t count, i;
    
        runtimeLock.assertWriting();
    
      ///按加载顺序加入数组
        classref_t *classlist = 
            _getObjc2NonlazyClassList(mhdr, &count);
        for (i = 0; i < count; i++) {
          ///加载子类之前先加载父类
            schedule_class_load(remapClass(classlist[i]));
        }
    ///按加载顺序加入数组
        category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
        for (i = 0; i < count; i++) {
            ///直接把分类加入数组最后面
            category_t *cat = categorylist[i];
            Class cls = remapClass(cat->cls);
            if (!cls) continue;  // category for ignored weak-linked class
           realizeClass(cls);
            assert(cls->ISA()->isRealized());
            add_category_to_
    }
    

    总结:

    1、先调用类的load方法

    • 按照编译顺序调用 (先编译,先调用)
    • 调用子类的+load方法之前,先调用父类的+load方法

    2、再调用分类的load方法

    • 按照编译顺序调用 (先编译,先调用)

    4.2、initialize

    [ɪˈnɪʃəlaɪz]

    • +initialize方法会在类第一次接收到消息的时候调用 且每个类只会初始化一次

      比如:[Student alloc]; 消息机制objc_msgSend方法 调用的时分类的方法

       [Student alloc];
      
     +[People(Test1) initialize]
     +[Student(Test2) initialize]
    
    • 会先调用父类的initilize(分类)在调用子类的initialize(先分类)的方法

    如果父类initialize 没有调用的话会调用,如果已经调用了那就不能在子类初始化的时候再调用

    向子类发送消息 会先向父类发送消息

     objc_msgSend(object_getClass([People alloc]), @selector(initialize))
            objc_msgSend(object_getClass([Student alloc]), @selector(initialize))
    

    证明:

    Method class_getInstanceMethod(Class cls, SEL sel)
    {
        lookUpImpOrNil(cls, sel, nil, 
                       NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
    }
    
    
    IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
    {
        IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    }
    
    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        IMP imp = nil;
    
        ///这个方法是否需要初始化 &&如果没有初始化
        if (initialize  &&  !cls->isInitialized()) {
            runtimeLock.unlockRead();
    //        初始化
            _class_initialize (_class_getNonMetaClass(cls, inst));
            runtimeLock.read();
        }
    
    
    void _class_initialize(Class cls)
    {
        assert(!cls->isMetaClass());
    
        Class supercls;
        bool reallyInitialize = NO;
    
        // Make sure super is done initializing BEFORE beginning to initialize cls.
        // See note about deadlock above.
        /// 如果存在父类,并且没有初始化 m那就初始化父类
        supercls = cls->superclass;
        if (supercls  &&  !supercls->isInitialized()) {
            _class_initialize(supercls);
        }
    #if __OBJC2__
            @try
    #endif
            {
                ///真真的初始化
                callInitialize(cls);
            }
    }
    
    
    void callInitialize(Class cls)
    {
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
        asm("");
    }
    

    5、initialize和load的区别

    +initialize 是通过objc_msgSend 方法进行调用的 +load在程序加载时直接找到函数指针进行调用的

    所以+initialize有以下特点

    • 如果子类没有实现+initialize方法会调用父类的initialize(所以父类的initialize会被调用多次)
    • 如果分类实现了+initialize方法会覆盖类本身的initialize调用
    @implementation People
    + (void)initialize{
        NSLog(@"%s",__func__);
    }
    @end
    
    @implementation Student //(继承People)
    + (void)initialize{
        NSLog(@"%s",__func__);
    }
    @end
    
    @implementation Animal //(继承People)
    + (void)initialize{
        NSLog(@"%s",__func__);
    }
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
          [Student alloc];
          [Animal alloc];
        }
        return 0;
    }
    
    
     +[People initialize]
     +[Student initialize]
     +[Animal initialize]
    

    为什么会是这个结果: [Student alloc]; 是会先去判断父类是否已初始化

    没有所以调用父类的

    objc_msgSend(object_getClass([People alloc]), **@selector**(initialize));

    在执行自己的

    objc_msgSend(object_getClass([Student alloc]), **@selector**(initialize));

    而[Animal alloc]; 时父类People已经初始化了所以只执行了自己的

    objc_msgSend(object_getClass([Animal alloc]), **@selector**(initialize));

    @implementation People
    + (void)initialize{
        NSLog(@"%s",__func__);
    }
    @end
    
    @implementation Student //(继承People)
    
    @end
    
    @implementation Animal //(继承People)
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
          [Student alloc];
          [Animal alloc];
        }
        return 0;
    }
    
    
     +[People initialize]
     +[People initialize]
     +[People initialize]
    

    为什么子类没有initialize会执行多次呢?

      [Student alloc];
            if (Student是否需要初始化&&Student没有初始化) {
                if (People是否需要初始化&&People有没有没有初始化) {
                    objc_msgSend(object_getClass([People alloc]), @selector(initialize))
                }
               objc_msgSend(object_getClass([Student alloc]), @selector(initialize))
            }
            
            
          [Animal alloc];
            if (Animal是否需要初始化&&Animal没有初始化) {
                if (People是否需要初始化&&People有没有没有初始化) {
                    objc_msgSend(object_getClass([People alloc]), @selector(initialize))
                }
               objc_msgSend(object_getClass([Animal alloc]), @selector(initialize))
            }
    

    也就是

    objc_msgSend(object_getClass([People alloc]), @selector(initialize))

    objc_msgSend(object_getClass([Student alloc]), @selector(initialize))

    根本找不到Student 的initialize方法 去父类中找所以People的initialize被调用

    objc_msgSend(object_getClass([Animal alloc]), @selector(initialize))

    根本找不到Animal 的initialize方法 去父类中找所以People的initialize被调用

    注意点 虽然[People initialize] 被执行了三次但并不代表父类被初始化了三次

    他是第一次对父类,其他两次是对子类进行的初始化

    6、Category中有+load方法吗?load方法是什么时候调用的?load方法能继承吗?(m)

    • 在runtime加载类、分类的时候调用这个方法
    • 可以继承 如果子类没有load放会调用父类的load 发送消息的流程

    ​ [Student load]; Student中没有 调用父类的 父类有分类调用分类的

    严格来讲不会主动调用load方法

    7、initialize和load的区别(m)

    • 调用方式的区别

      • load是直接通过函数指针的方式去调用 initialize是使用的消息机制objc_msgSend
    • 调用时机不同

      • load是在runtime加载类、分类的时候调用且之后调用一次
      • initialize 是在类第一次接收消息的时候调用,而且某一个类只会initialize一次,但是父类的initialize可能会被调用多次

    8、initialize和load的调用顺序(m)

    • load先调用类的load方法 且先编译的类的先调用 且调用子类的load之前会先调用父类的load方法

    • 在调用分类的load方法 且先编译的分类的先调用

    • initialize 先初始化父类 在初始化子类 如果子类没有initialize会调用父类的

  • 相关阅读:
    洛谷 P1990 覆盖墙壁
    洛谷 P1033 自由落体
    洛谷 P2049 魔术棋子
    洛谷 P2183 巧克力
    poj_1743_Musical Theme(后缀数组)
    Codeforces Round #367 (Div. 2) D. Vasiliy's Multiset
    Codeforces Round #367 (Div. 2) C. Hard problem
    hdu_5831_Rikka with Parenthesis II(模拟)
    hdu_5826_physics(物理题)
    hdu_5821_Ball(贪心)
  • 原文地址:https://www.cnblogs.com/xiaowuqing/p/14027426.html
Copyright © 2020-2023  润新知