Category

作者: 英雄出少年 | 来源:发表于2019-05-07 18:02 被阅读0次
    Category的使用场合是什么?

    为某个类拓展方法,分模块。

    Category的实现原理

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

    category内部结构
    源码解读顺序
    • objc-os.mm

      • _objc_init
      • map_images
      • map_images_nolock
    • objc-runtime-new.mm

      • _read_images
      • remethodizeClass
      • attachCategories
      • attachLists
      • realloc、memmove、 memcpy
    • 分类数据合并(实现源码)
    // Attach method lists and properties and protocols from categories to a class.
    // Assumes the categories in cats are all loaded and sorted by load order, 
    // oldest categories first.
    static void 
    attachCategories(Class cls, category_list *cats, bool flush_caches)
    {
        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));
    
        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;
            }
        }
        //得到类对象里的数据
        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);
    }
    
    
    • 方法合并(实现源码)
    //addedCount 分类的个数
        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];
            } 
            else {
                // 1 list -> many lists
                List* oldList = list;
                uint32_t oldCount = oldList ? 1 : 0;
                uint32_t newCount = oldCount + addedCount;
                setArray((array_t *)malloc(array_t::byteSize(newCount)));
                array()->count = newCount;
                if (oldList) array()->lists[addedCount] = oldList;
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));
            }
        }
    
    方法合并图解
    Category和Class Extension的区别是什么?

    Class Extension在编译的时候,它的数据就已经包含在类信息中
    Category是在运行时,才会将数据合并到类信息中

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

    有load方法
    load方法在runtime加载类、分类的时候调用
    load方法是通过函数地址直接调用,所以是不是分类调用会,其他方法是一般通过消息机制调用也就是通过isa指针层层寻找方法
    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函数调用

    /*********************************************************************** 底层源码解读
    * call_load_methods
    **********************************************************************/
    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();//先调用类的load方法
            }
    
            // 2. Call category +loads ONCE
            more_categories = call_category_loads(); //再调用分类的load方法
    
            // 3. Run more +loads if there are classes OR more untried categories
        } while (loadable_classes_used > 0  ||  more_categories);
    
        objc_autoreleasePoolPop(pool);
    
        loading = NO;
    }
    
    

    ** call_class_loads**

    /*********************************************************************** 底层源码解读
    * call_class_loads
    **********************************************************************/
    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 方法
            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);
    }
    
    

    ** call_category_loads**

    /*********************************************************************** 底层源码解读
    * call_category_loads
    **********************************************************************/
    static bool call_category_loads(void)
    {
        int i, shift;
        bool new_categories_added = NO;
        
        // Detach current loadable list.
        struct loadable_category *cats = loadable_categories;
        int used = loadable_categories_used;
        int allocated = loadable_categories_allocated;
        loadable_categories = nil;
        loadable_categories_allocated = 0;
        loadable_categories_used = 0;
    
        // Call all +loads for the detached list.
        for (i = 0; i < used; i++) {
            Category cat = cats[i].cat;
            load_method_t load_method = (load_method_t)cats[i].method;
            Class cls;
            if (!cat) continue;
    
            cls = _category_getClass(cat);
            if (cls  &&  cls->isLoadable()) {
                if (PrintLoading) {
                    _objc_inform("LOAD: +[%s(%s) load]\n", 
                                 cls->nameForLogging(), 
                                 _category_getName(cat));
                }
                (*load_method)(cls, SEL_load);//获取方法地址直接调用
                cats[i].cat = nil;
            }
        }
        // Copy any new +load candidates from the new list to the detached list.
        new_categories_added = (loadable_categories_used > 0);
        for (i = 0; i < loadable_categories_used; i++) {
            if (used == allocated) {
                allocated = allocated*2 + 16;
                cats = (struct loadable_category *)
                    realloc(cats, allocated *
                                      sizeof(struct loadable_category));
            }  
            cats[used++] = loadable_categories[I];
        }
        return new_categories_added;
    }
    
    

    类的加载顺序

    static void schedule_class_load(Class cls)
    {
        if (!cls) return;
        assert(cls->isRealized());  // _read_images should realize
    
        if (cls->data()->flags & RW_LOADED) return;
    
        // 递归调用
        schedule_class_load(cls->superclass);
       //cls 添加到 loadable_classes数组的后面
        add_class_to_loadable_list(cls);
        cls->setInfo(RW_LOADED); 
    }
    

    ** prepare_load_methods**

    void prepare_load_methods(const headerType *mhdr)
    {
        size_t count, I;
    
        runtimeLock.assertLocked();
        //按照编译顺序去加载类
        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_loadable_list(cat);
        }
    }
    
    memmove、memcpy的区别

    相同
    作用是拷贝一定长度的内存的内容

    不同
    当内存发生局部重叠的时候,memmove保证数据的完整性,memcpy不保证拷贝的结果的正确

    void *memcpy(void *dst, const void *src, size_t count);
    void *memmove(void *dst, const void *src, size_t count);
    
    load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?

    调用方式

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

    调用时刻

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

    load、initialize的调用顺序?
    + load
    先调用类的load

    • 先编译的类,优先调用load
    • 调用子类的load之前,会先调用父类的load

    再调用分类的load

    • 先编译的分类,优先调用load

    initialize

    • 先初始化父类
    • 再初始化子类(可能最终调用的是父类的initialize方法)

    objc4源码解读

    • -msg-arm64.s

      • objc_msgSend
    • objc-runtime-new.mm

      • class_getInstanceMethod
      • lookUpImpOrNil
      • lookUpImpOrForward
      • _class_initialize
      • callInitialize
      • objc_msgSend(cls, SEL_initialize)
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            [TGStudent alloc];
            BOOL sutdentInitialized = NO;
            BOOL personInitialized = NO;
            if (!sutdentInitialized) {//自己有没有初始化
                if (!personInitialized) {//父类有没有初始化
                    objc_msgSend([TGPerson class], @selector(initialize));
                    personInitialized = YES;
                }
                objc_msgSend([TGStudent class], @selector(initialize));
                sutdentInitialized = YES;
            }
    }
    return 0;
    }
    
    Category能否添加成员变量?如果可以,如何给Category添加成员变量?

    不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果

    相关文章

      网友评论

          本文标题:Category

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