美文网首页
Category总结

Category总结

作者: 斑驳的流年无法释怀 | 来源:发表于2018-08-18 12:16 被阅读8次

    Category的底层结构

    定义在objc-runtime-new.h中

    struct category_t {
        const char *name;//类名 LQPerson
        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);
    };
    

    生成C++代码中可以看到如下的结构体

    static struct _category_t _OBJC_$_CATEGORY_LQPerson_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) =
    {
        "LQPerson",
        0, // &OBJC_CLASS_$_LQPerson,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_LQPerson_$_Eat,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_LQPerson_$_Eat,
        (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_LQPerson_$_Eat,
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_LQPerson_$_Eat,
    };
    

    Category的加载处理过程

    1. 通过Runtime加载某个类的所有Category数据
    2. 把所有Category的方法、属性、协议数据,合并到一个大数组中
      后面参与编译的Category数据,会在数组的前面
    3. 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
    源码解读顺序
    objc-os.mm
    _objc_init
    map_images
    map_images_nolock
    
    objc-runtime-new.mm
    _read_images
    remethodizeClass
    attachCategories
    attachLists
    realloc、memmove、 memcpy
    

    分类方法如何附加到类对象中,请看如下函数调用

    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_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--) {//注意这里是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;
            }
        }
        //得到类对象里面的数据
        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 原来的方法列表, 往后挪动了addedCount
                memmove(array()->lists + addedCount, array()->lists, 
                        oldCount * sizeof(array()->lists[0]));
                //addedLists 所有分类的方法列表
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));
            }
    
    • move是直接挪动 能够保证以前的数据完整的挪动过去
    • mempy是一个一个的拷贝,随便覆盖,不会存在数据丢失

    category的方法
    不是真的覆盖方法,而是顺序在前面了,找到了分类的方法就不再去找了,即使类里面有这个方法也没有机会调用

    分类和类扩展的区别
    类扩展:编译就已经存在里面了
    分类:runtime

    +load方法

    调用时机:

    +load方法会在runtime加载类、分类时调用
    每个类、分类的+load,在程序运行过程中只调用一次

    调用顺序
    先调用类的+load
    按照编译先后顺序调用(先编译,先调用)
    调用子类的+load之前会先调用父类的+load
    再调用分类的+load
    按照编译先后顺序调用(先编译,先调用)

    objc4源码解读过程:objc-os.mm
    _objc_init
    
    load_images
    
    prepare_load_methods
    schedule_class_load
    add_class_to_loadable_list
    add_category_to_loadable_list
    
    call_load_methods
    call_class_loads
    call_category_loads
    (*load_method)(cls, SEL_load)
    
    +load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
    
    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);//+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
        }
        
        // Destroy the detached list.
        if (classes) free(classes);
    }
    

    +initialize方法

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

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

    objc4源码解读过程
    objc-msg-arm64.s
    objc_msgSend
    
    objc-runtime-new.mm
    class_getInstanceMethod
    lookUpImpOrNil
    lookUpImpOrForward
    _class_initialize
    callInitialize
    objc_msgSend(cls, SEL_initialize)
    
    
    void callInitialize(Class cls)
    {
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
        asm("");//说明initialize方法是通过消息发送机制调用的
    }
    

    总结

    load、initialize方法的区别什么?
    1.调用方式
    1> load是根据函数地址直接调用
    2> initialize是通过objc_msgSend调用
    
    2.调用时刻
    1> load是runtime加载类、分类的时候调用(只会调用1次)
    2> initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
    
    load、initialize的调用顺序?
    1.load
    1> 先调用类的load
    a) 先编译的类,优先调用load
    b) 调用子类的load之前,会先调用父类的load
    
    2> 再调用分类的load
    a) 先编译的分类,优先调用load
    
    2.initialize
    1> 先初始化父类
    2> 再初始化子类(可能最终调用的是父类的initialize方法)
    

    QA

    • Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?

    Category中有load方法,load方法在程序启动装载类信息的时候就会调用。load方法可以继承。调用子类的load方法之前,会先调用父类的load方法

    • load、initialize的区别,以及它们在category重写的时候的调用的次序。

    区别在于调用方式和调用时刻
    调用方式:load是根据函数地址直接调用,initialize是通过objc_msgSend调用
    调用时刻:load是runtime加载类、分类的时候调用(只会调用1次),initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
    调用顺序:先调用类的load方法,先编译那个类,就先调用load。在调用load之前会先调用父类的load方法。分类中load方法不会覆盖本类的load方法,先编译的分类优先调用load方法。initialize先初始化父类,之后再初始化子类。如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次),如果分类实现了+initialize,就覆盖类本身的+initialize调用。

    相关文章

      网友评论

          本文标题:Category总结

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