01-03 category 原理概述

1、Category

1.1、原理

#import "FQPeople+Test.h"
@implementation FQPeople (Test)
+ (void)test{
    
}
- (void)test1{
    
}
@end
  • 分类的对象方法test1存放在类对象FQPeople中

  • 分类的类方法test存在FQPeople元类中

使用:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc FQPeople+Test.m

在编译时生成c++代码

每一个分类都会产生一个结构体

XFPeople XFPeople+Category1 XFPeople+Category2

产生两个结构体

struct _category_t {
	const char *name;//类名FQPeople
	struct _class_t *cls;
	const struct _method_list_t *instance_methods;
	const struct _method_list_t *class_methods;
	const struct _protocol_list_t *protocols;
	const struct _prop_list_t *properties;
};

在方法编译时 分类的方法,数据等都存在编译之后的结构体中即_category_t中

在运行时通过runtime动态的将分类的方法合并到类对象、元类对象中 在程序启动的时候就合并

而不是在使用该分类 或者编译时调用的时候才合并

通过这个流程得知 依次插入

XFPeople XFPeople+Category1 XFPeople+Category2

着三个中都有-(void)test{}方法,会先从分类中找方法,如果分类中没有再从对象中找方法 (数组遍历从索引0开始的)

源码:

至于是先执行 XFPeople+Category1 或者是 XFPeople+Category2中的方法,则存在不确定性,这个要看哪个先编译,最后编译的放在最前面

总结整体流程:

1、通过runtime加载某一个类的所有分类数据

2、把所有的category的方法属性协议合并到一个数组中(后编译的分类数据会放在数组的前面)

3、将合并后的分类数据(方法,属性,协议)插入到类原来数据的前面

最后粘贴一下运行时源码
最后粘贴一下运行时源码

/*
 类对象,分类列表
 cls XQPeople
 cats = [category_t(Test),category_t(Eat)]
 */
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-t,method-t],[method-t,method-t]
     ]
     */
    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--) {
        ///取出某一个分类
        auto& entry = cats->list[i];
///取出分类中的对象方法(或者元类方法)  最终把所有的分类方法列表放入mlists大数组中
        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 原来的方法列表
            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];
        } 
        ...

1.2、扩展(类扩展)

@interface FQPeople()

@end
@implementation FQPeople

@end

类扩展是在编译的时候就把扩展的信息合并到类里面去,而分类是在运行时合并的

2、Category实现原理(m)

  • category 编译之后的底层时显示一个 struct category_t 类型的结构体

    里面存放的是分类的对象方法列表 类方法列表 协议方法列表 属性列表 类名 ,类对象

struct _category_t {
	const char *name;
	struct _class_t *cls;
	const struct _method_list_t *instance_methods;
	const struct _method_list_t *class_methods;
	const struct _protocol_list_t *protocols;
	const struct _prop_list_t *properties;
};
  • 运行时(程序运行或者启动的时候)会将分类的数据合并到原来的类信息中(类对象、元类对象)

3、Category 和class extension 的区别(m)

  • class extension是在编译的时候就把扩展的信息合并到类里面去,

  • Category是在运行时将数据合并到类信息中

4、load initialize

4.1、load的原理

  • load方法在在runtime加载类、分类的时候调用

相当于运行时程序一旦启动的时候进行调用,加载类的时候调用类的load 加载分类的时候调用分类的load,

  • 每个类、分类的+load,在程序运行过程中只调用一次;

4.1.1、不存在继承关系的load

证明

@implementation People (Test1)
+ (void)load{
    NSLog(@"%s",__func__);
}
+ (void)test{
    NSLog(@"%s",__func__);
}
@end
@implementation People (Test2)
+ (void)load{
    NSLog(@"%s",__func__);
}
+ (void)test{
    NSLog(@"%s",__func__);
}
@end
@implementation People
+ (void)load{
    NSLog(@"%s",__func__);
}
+ (void)test{
    NSLog(@"%s",__func__);
}

直接运行

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    }
    return 0;
}

+[People load]
+[People(Test2) load]
+[People(Test1) load]

可以看出 只要有load方法在程序启动的时候就会调用

void  printMethodNamesOfCalss(Class cls){
    unsigned int count;
    Method *methodList = class_copyMethodList(cls, &count);
    NSMutableString *methodsNames = [NSMutableString string];
    for (int i = 0; i<count; i++) {
        Method method = methodList[i];
        NSString *methodsName = NSStringFromSelector(method_getName(method));
        [methodsNames appendString:methodsName];
        [methodsNames appendString:@", "];
    }
    free(methodList);
    NSLog(@"%@   %@",cls,methodsNames);
    
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
      printMethodNamesOfCalss(object_getClass([People class]));
      [People test];
    }
    return 0;
}
 程序启动加载时(runtime)调用load
 +[People load]
 +[People(Test2) load]
 +[People(Test1) load]
 ///打印出对象和分类对象的方法列表  可以看得有多个load 多个test
 People        load, test,       load, test,         load, test,

 ///[People test]; 只调用了Test2的test方法
 +[People(Test2) test]

调用顺序

  • 类的load方法

疑问:同样是类方法,且都在类方法列表中存在 为什么load调用了三次 而test只调用了分类中的呢?

void call_load_methods(void){
    do {
        //先调用类的load方法
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }
        // 2. Call category +loads ONCE 再调动分类的load方法
        more_categories = call_category_loads();
        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

  
static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list. 所有有load的类的列表
    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.遍历load类列表
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
//        取出该load的load类方法指针
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]
", cls->nameForLogging());
        }
        ///直接调用该load方法
        (*load_method)(cls, SEL_load);
    }
    // Destroy the detached list.
    if (classes) free(classes);
}
typedef void(*load_method_t)(id, SEL);

struct loadable_class {
    Class cls;  // may be nil
    IMP method;//这个里面存放的就是类load方法
};

struct loadable_category {
    Category cat;  // may be nil
    IMP method;//这个里面存放的就是分类load方法
};

答:为什么load会执行三次

因为在运行时时期 会遍历所有带有load方法的类,直接拿到load方法的地址 直接调用

而+(void)test{}呢?

这个方法是通过发送消息

objc_msgSend(objc_getClass("People"), sel_registerName("test"));

4.1.2、存在继承关系的load

  • 先调用类load方法 1000个类的话,那先调用那个类呢?

    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  递归调用 先调用s父类
        schedule_class_load(cls->superclass);
    
        ///把父类加入到包含load类的列表中 列表中存储类对象
        add_class_to_loadable_list(cls);
        cls->setInfo(RW_LOADED); 
    }
    
    

    首先会先调用父类的load方法 在调用子类的load方法

    如果不存在继承关系呢?

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertWriting();

  ///按加载顺序加入数组
    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_
}

总结:

1、先调用类的load方法

  • 按照编译顺序调用 (先编译,先调用)
  • 调用子类的+load方法之前,先调用父类的+load方法

2、再调用分类的load方法

  • 按照编译顺序调用 (先编译,先调用)

4.2、initialize

[ɪˈnɪʃəlaɪz]

  • +initialize方法会在类第一次接收到消息的时候调用 且每个类只会初始化一次

    比如:[Student alloc]; 消息机制objc_msgSend方法 调用的时分类的方法

     [Student alloc];
    
 +[People(Test1) initialize]
 +[Student(Test2) initialize]
  • 会先调用父类的initilize(分类)在调用子类的initialize(先分类)的方法

如果父类initialize 没有调用的话会调用,如果已经调用了那就不能在子类初始化的时候再调用

向子类发送消息 会先向父类发送消息

 objc_msgSend(object_getClass([People alloc]), @selector(initialize))
        objc_msgSend(object_getClass([Student alloc]), @selector(initialize))

证明:

Method class_getInstanceMethod(Class cls, SEL sel)
{
    lookUpImpOrNil(cls, sel, nil, 
                   NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;

    ///这个方法是否需要初始化 &&如果没有初始化
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
//        初始化
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
    }

void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    /// 如果存在父类,并且没有初始化 m那就初始化父类
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
#if __OBJC2__
        @try
#endif
        {
            ///真真的初始化
            callInitialize(cls);
        }
}

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

5、initialize和load的区别

+initialize 是通过objc_msgSend 方法进行调用的 +load在程序加载时直接找到函数指针进行调用的

所以+initialize有以下特点

  • 如果子类没有实现+initialize方法会调用父类的initialize(所以父类的initialize会被调用多次)
  • 如果分类实现了+initialize方法会覆盖类本身的initialize调用
@implementation People
+ (void)initialize{
    NSLog(@"%s",__func__);
}
@end
@implementation Student //(继承People)
+ (void)initialize{
    NSLog(@"%s",__func__);
}
@end
@implementation Animal //(继承People)
+ (void)initialize{
    NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
      [Student alloc];
      [Animal alloc];
    }
    return 0;
}

 +[People initialize]
 +[Student initialize]
 +[Animal initialize]

为什么会是这个结果: [Student alloc]; 是会先去判断父类是否已初始化

没有所以调用父类的

objc_msgSend(object_getClass([People alloc]), **@selector**(initialize));

在执行自己的

objc_msgSend(object_getClass([Student alloc]), **@selector**(initialize));

而[Animal alloc]; 时父类People已经初始化了所以只执行了自己的

objc_msgSend(object_getClass([Animal alloc]), **@selector**(initialize));

@implementation People
+ (void)initialize{
    NSLog(@"%s",__func__);
}
@end
@implementation Student //(继承People)

@end
@implementation Animal //(继承People)

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
      [Student alloc];
      [Animal alloc];
    }
    return 0;
}

 +[People initialize]
 +[People initialize]
 +[People initialize]

为什么子类没有initialize会执行多次呢?

  [Student alloc];
        if (Student是否需要初始化&&Student没有初始化) {
            if (People是否需要初始化&&People有没有没有初始化) {
                objc_msgSend(object_getClass([People alloc]), @selector(initialize))
            }
           objc_msgSend(object_getClass([Student alloc]), @selector(initialize))
        }
        
        
      [Animal alloc];
        if (Animal是否需要初始化&&Animal没有初始化) {
            if (People是否需要初始化&&People有没有没有初始化) {
                objc_msgSend(object_getClass([People alloc]), @selector(initialize))
            }
           objc_msgSend(object_getClass([Animal alloc]), @selector(initialize))
        }

也就是

objc_msgSend(object_getClass([People alloc]), @selector(initialize))

objc_msgSend(object_getClass([Student alloc]), @selector(initialize))

根本找不到Student 的initialize方法 去父类中找所以People的initialize被调用

objc_msgSend(object_getClass([Animal alloc]), @selector(initialize))

根本找不到Animal 的initialize方法 去父类中找所以People的initialize被调用

注意点 虽然[People initialize] 被执行了三次但并不代表父类被初始化了三次

他是第一次对父类,其他两次是对子类进行的初始化

6、Category中有+load方法吗?load方法是什么时候调用的?load方法能继承吗?(m)

  • 在runtime加载类、分类的时候调用这个方法
  • 可以继承 如果子类没有load放会调用父类的load 发送消息的流程

​ [Student load]; Student中没有 调用父类的 父类有分类调用分类的

严格来讲不会主动调用load方法

7、initialize和load的区别(m)

  • 调用方式的区别

    • load是直接通过函数指针的方式去调用 initialize是使用的消息机制objc_msgSend
  • 调用时机不同

    • load是在runtime加载类、分类的时候调用且之后调用一次
    • initialize 是在类第一次接收消息的时候调用,而且某一个类只会initialize一次,但是父类的initialize可能会被调用多次

8、initialize和load的调用顺序(m)

  • load先调用类的load方法 且先编译的类的先调用 且调用子类的load之前会先调用父类的load方法

  • 在调用分类的load方法 且先编译的分类的先调用

  • initialize 先初始化父类 在初始化子类 如果子类没有initialize会调用父类的

原文地址:https://www.cnblogs.com/xiaowuqing/p/14027426.html