Category总结

2018-08-18  本文已影响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]));
        }

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方法可以继承。调用子类的load方法之前,会先调用父类的load方法

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

上一篇下一篇

猜你喜欢

热点阅读