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