iOS Category底层原理 、+load 、+initialize

Category底层原理

Category可以把一个类的功能拆解成很多模块

创建一个类,并创建两个分类


Snip20200706_24.png

分类编译时底层编译成的代码:

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;   //  属性列表
};

每一个分类对应一个结构体对象

如:

#import "MJPerson+Test.h"

@implementation MJPerson (Test)

- (void)run
{
    NSLog(@"MJPerson (Test) - run");
}


- (void)test
{
    NSLog(@"test");
}

+ (void)test2
{
    
}

编译成C++代码

static struct _category_t _OBJC_$_CATEGORY_MJPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
    "MJPerson",
    0, // &OBJC_CLASS_$_MJPerson,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MJPerson_$_Test,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_MJPerson_$_Test,
    0,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MJPerson_$_Test,
};

// 对象方法
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_MJPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"test", "v16@0:8", (void *)_I_MJPerson_Test_test}}
};

// 类方法
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_MJPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"test2", "v16@0:8", (void *)_C_MJPerson_Test_test2}}
};

// 属性列表
static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_MJPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    2,
    {{"weight","Ti,N"},
    {"height","Td,N"}}
};

分类里的属性、方法、协议等最后也是通过runtime动态合并到类对象,元类对象中
具体步骤:


Snip20200706_19.png

源码下载地址 https://opensource.apple.com/tarballs/objc4

如方法的合并:
runtime 会将Person的所有分类的方法列表先合并到一个列表里面,然后再通过内存移动插入到原来Person方法列表的前面


Snip20200706_21.png

至于Test1 和 Test2 谁在前 谁在后,根据编译循序来的因为 合并分类列表的代码为

 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();
        }

通过源码可知,后编译的在前面,至于编译循序可以在下面调整


Snip20200706_23.png

所以如果在Person 和 两个分类中都有一个相同的方法 如 run,以上图的编译循序 是执行 Test1中的方法。这个并不是方法覆盖,而是,方法查找的时候会先查到Test1中的方法

和类扩展的区别 Extension(OC)

类扩展是在编译的时候就将方法 属性等加入到原先的方法、属性列表中了

分类添加属性相关

1、当我们给一个类添加属性的时候如

@property (nonatomic, assign) int age;
// 给一个类添加age 属性

会默认实现下面3个步骤

// 1、声明一个成员变量
{
   int _age;
}
//2、 声明set 和 get 方法
- (void)setAge:(int)age;
- (int)age;

//3、实现get 和set 方法
- (void)setAge:(int)age {
    _age = age;
}

- (int)age {
    return _age;
}

2、当我们给分类添加属性时,默认只有方法的声明

@property (assign, nonatomic) int weight;
//默认声明
- (void)setWeight:(int)weight;
- (int)weight;

但是没有实现,所以可以调用,但是会报错找不到方法

  Person *person = [Person new];
        person.weight = 10;
        NSLog(@"%d",person.weight);
//-[Person weight]: unrecognized selector sent to instance 0x10067f080'

我们如果手动加上成员变量 实现set 和get 方法
编译时就会报错,分类中不能添加成员变量


Snip20200706_31.png

从上面的分类编译成的底层代码也可以发现,根本没有成员变量列表,
下图是一个普通类的底层结构。有个成员变量列表


Snip20200706_32.png

3、给分类添加关联对象实现类似成员变量的功能

实现属性的set 和 get 方法

// 地址值
static const void *PersonNameKey = &PersonNameKey;
/**
  加上static 防止外面访问,否则别的地方
  通过 extern const void *PersonNameKey; 可以访问到
 */


- (void)setName:(NSString *)name {
//objc_AssociationPolicy 关联策略 类似于用什么修饰 copy assign strong 等
//    objc_setAssociatedObject(id  _Nonnull object, const void * _Nonnull key, id  _Nullable value, objc_AssociationPolicy policy)

    objc_setAssociatedObject(self, PersonNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, PersonNameKey);
}

其中const void * _Nonnull key 是一个地址值可以有很多种办法生成

//static const char LQNameKey;
- (void)setName:(NSString *)name {
//1、 使用get方法的@selector
 objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);

// 2、使用属性名称
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
// 3、使用一个字符
objc_setAssociatedObject(self, &LQNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

objc_AssociationPolicy 对应的修饰符

Snip20200707_2.png

关联对象的原理

void objc_setAssociatedObject(id object, const void * key,
                              id value, 
                 objc_AssociationPolicy policy)

实现关联对象技术的核心对象有
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
runtime 管理着一个全局的AssociationsManager,内部管理一个map(AssociationsHashMap),这个map的key是根据object生成的,value对应的是另一个map(ObjectAssociationMap),objectMap的key就是上面方法传进来的key,value对应ObjcAssociation对象,ObjcAssociation内部包含policy和真正的value


Snip20200707_4.png

+load方法

一、+load 方法会在runtime加载类和分类时调用
二调用顺序
1.先调用类的+load
按照编译顺序调用(先编译,先调用)
调用子类的+load方法之前,如果父类的+load没调用过就先调用父类的+load方法
2、所有类的+load方法调用完,再调用分类的+load
按照编译顺序调用(先编译,先调用)(没有父类,子类之分)

3、底层代码
先调用父类的+load 方法的原因 准备调用
prepare_load_methods会调用下面的代码

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
    schedule_class_load(cls->superclass);//递归调用

    add_class_to_loadable_list(cls);// 加入类load列表中
    cls->setInfo(RW_LOADED); 
}

 do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads(); // 调用类的
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads(); // 调用分类的

        // 3. Run more +loads if there are classes OR more untried categories
    }

call_class_loads 简化为

static void call_class_loads(void)
{

    struct loadable_class *classes = loadable_classes;
    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; 
        (*load_method)(cls, SEL_load);
    }
}

call_category_loads 调用分类的+load

static bool call_category_loads(void) {
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;
            (*load_method)(cls, SEL_load); // 直接通过函数指针调用
}

load_method_t

struct loadable_class {
    Class cls;  // may be nil
    IMP method; // load方法
};

struct loadable_category {
    Category cat;  // may be nil
    IMP method; //load方法
};

所以load方法是直接找到,然后通过函数指针调用的,不像上面的run方法 通过objc_msgSend调用,通过isa指针找方法列表

+initialize方法

调用顺序

+initialize方法会在类第一次接收到消息时调用
1、先调用父类的initialize, 再调用子类的+initialize
2、(先初始化父类,再初始化子类,每个类只会初始化1次)

源码解读过程

objc4源码解读过程
objc-msg-arm64.s
objc_msgSend

objc-runtime-new.mm
class_getInstanceMethod
lookUpImpOrNil
lookUpImpOrForward
_class_initialize
callInitialize
objc_msgSend(cls, SEL_initialize)

源码

每次调用objc_msgSend 会调用

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
 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;
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) { // 如果父类没有初始化,先递归初始化父类
        _class_initialize(supercls);
    }
            callInitialize(cls);
  }

// 最终初始化 也是通过objc_msgSend
void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

注意

+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点
如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
如果分类实现了+initialize,就覆盖类本身的+initialize调用

+load 和 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方法)

你可能感兴趣的:(iOS Category底层原理 、+load 、+initialize)