分类

分类的原理

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]\n", 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方法)
posted @ 2020-08-13 22:46  菜鸟工程司  阅读(308)  评论(0编辑  收藏  举报