zoukankan      html  css  js  c++  java
  • 分类

    分类的原理

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

    category_t的底层结构:

    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;
        // Fields below this point are not always present on disk.
        struct property_list_t *_classProperties;
    
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
    
        property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    };
    

    分类的加载过程:

    1. 通过Runtime加载某个类的所有Category数据

    2. 把所有Category的方法、属性、协议数据,合并到一个大数组中
      后面参与编译的Category数据,会在数组的前面

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

        category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/); //加载所有分类
        attachCategories(cls, cats, false /*don't flush caches*/);//加载分类数据
    
    static void 
    attachCategories(Class cls, category_list *cats, bool flush_caches)
    {
      
        //...省略
        
        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;
                memmove(array()->lists + addedCount, array()->lists, 
                        oldCount * sizeof(array()->lists[0]));//原先的移动到最后
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));//复制到数组前面
     }
    

    +load方法

    +load方法会在runtime加载类、分类时调用,每个类,分类的+load方法只会调用一次

    调用顺序

    1. 先调用类的load(先编译,先调用),调用子类的load之前先调用父类的load
    2. 调用分类的load(先编译,先调用)

    源码(有所精简):

    load_images(const char *path __unused, const struct mach_header *mh)
    {
        //...
        
        // 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方法
    }
    
    void prepare_load_methods(const headerType *mhdr)
    {
        //...
        
        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_loadable_list(cat);//添加分类方法
        }
    }
    
    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); 
    }
    
    void call_load_methods(void)
    {
       //....
      
      do {        // 1. 调用类的load方法
            while (loadable_classes_used > 0){
                call_class_loads();
            }
    
    
            // 2. 调用分类的方法,仅一次
            more_categories = call_category_loads();
    
          
        } while (loadable_classes_used > 0  ||  more_categories);
    
       
    

    load方法是通过函数直接调用

    static void call_class_loads(void)
    {
        int i;
        
     //...
        // 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]
    ", cls->nameForLogging());
            }
            //通过函数指针直接调用
            (*load_method)(cls, SEL_load);
        }
        
        // Destroy the detached list.
        if (classes) free(classes);
    }
    

    +initialize方法

    +initialize方法会在类第一次接收到消息时调用

    调用顺序:
    先调用父类的+initialize,再调用子类的+initialize
    (先初始化父类,再初始化子类,每个类只会初始化1次)

    +initialize通过objc_msgSend进行调用所以有如下特点:
    如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
    如果分类实现了+initialize,就覆盖类本身的+initialize调用

    面试题

    1. load、initialize方法的区别什么?
    • 调用方式

      • load是根据函数地址直接调用
      • initialize是通过objc_msgSend调用
    • 调用时刻

      • load是runtime加载类、分类的时候调用(只会调用1次)
      • initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
    1. load、initialize的调用顺序?
    • load

      1. 先调用类的load
        a) 先编译的类,优先调用load
        b) 调用子类的load之前,会先调用父类的load

      2. 再调用分类的load
        a) 先编译的分类,优先调用load

    • initialize

    1. 先初始化父类
    2. 再初始化子类(可能最终调用的是父类的initialize方法)
  • 相关阅读:
    sharedWorker 实现多页面通信
    cookie跨域那些事儿
    event loop整理
    tsConfig/baseUrl -- 一键告别相对路径import
    vscode配置golang开发环境手把手描述篇
    Vue学习笔记二
    Vue学习笔记
    echarts迁移图动态加载
    病虫害可视化监测平台(一)
    昆虫识别开发进展APP(四)
  • 原文地址:https://www.cnblogs.com/cnman/p/13499418.html
Copyright © 2011-2022 走看看