iOS:Runtime之Category、Extension、load、initialize

大家好,我是OB!

今天来聊聊Runtime和四剑客(Category、Extension、load、initialize)爱恨情仇!

一、category

1、category中的方法会覆盖主类的方法吗

先创建一个Person类和

先看现象
编译时我们可以发现,主类先开始compile,然后才是compile 分类。所以从两个角度说:

a:宏观(现象)角度:分类会覆盖主类的方法!

当分类和主类同时实现- (void)walk;方法时,调用结果是调用的分类里面的方法,不管是哪个分类,都优先与主类方法;

b:微观(源码)角度:分类不会覆盖主类的方法!

虽然优先调用分类的方法。但是主类的方法依然存在(分类方法和主类的方法都在一个metho_list中)。只不过调用优先级比分类的低,当向Person发送一个walk消息时,由于在分类中找到了该方法的实现,所以不再继续查找方法实现了,也就不会在调用主类里面的方法了,因此现象上是被覆盖。本质是分类方法通过runtime合并到了method_list的前面,分类优先调用,导致主类不会执行。

2、category加载过程

程序编译过后,所有的category会被编译成结构体category_t

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
};

编译时,category中的方法还没有添加到类对象,或者元类对象中。只有运行时,动态的添加到类对象或者元类对象;以下摘抄自源码。

可以看出,后编译的categorywhile中先取出来添加到类对象的方法列表中,由于后面还有method_t扩容,而且category的方法插入到主类方法列表前面。所以同样的方法,先响应category的方法,然后停止,自然不能响应主类中的方法,造成了被覆盖的假象。


method_list_t **mlists = malloc(...)
property_list_t **proplists = malloc(...) 
protocol_list_t **protolists =  malloc(...)
int i = category_t_s->count;        
while (i--) {
	auto& entry = category_t_s->list[i];
	method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
  	mlists[mcount++] = mlist;
	fromBundle |= entry.hi->isBundle();

	property_list_t *proplist = 
	entry.cat->propertiesForMeta(isMeta, entry.hi);
	proplists[propcount++] = proplist;

	protocol_list_t *protolist = entry.cat->protocols;
	protolists[protocount++] = protolist;
}


//array()->lists 原始method list
memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
//addedLists category method list
memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));

对了,category的compile的顺序在这里

在这里插入图片描述

所以:对于编译顺序,当父类和本类的load方法调用完成了,然后就是先编译的category的load方法先执行,后编译后执行; 但是对于普通方法,由于i-- ,所以普通category的方法调用顺序是相反的,就是先编译的category方法排在主列表后面,也就是后编译的方法,先执行

3、category能添加属性吗?

不能,可以用@property (nonatomic, copy)NSString *name;添加属性,但是它不会生成setNamename方法的实现,也不会生成 成员变量_name

为什么不能呢?

因为对象的内存布局不能被破坏,我们通过objc_setAssociatedObject关联的对象,也不是生成了属性,而是重新开辟了内存,通过key关联到了当前对象。所以当我们objc_setAssociatedObject后,再通过runtime的Ivar * ivars = class_copyIvarList(person.class, &count);遍历属性也会发现,没有动态添加的关联对象。

二、Extension

这个不陌生,类的拓展在.m文件中添加,主要是私有化property方法声明

类的拓展在编译时就已经将其添加的property,**方法声明添加到主类的类对象或者元类对象**了

//这就是 Person类的拓展
@interface Person()

@end

@implementation Person

- (void)walk {
    NSLog(@"我在主类中:%s",__func__);
}
@end

三、load

load方法在运行时,由runtime调用,并且只要类存在在项目中,不管有没有用到,也不管有没有import,都会调用load。

由于源码利用递归,所以先添加父类,在添加子类到数组里面,最后在while循环去加载load,

所以总的顺序:先加载某个类的父类再加载子类,最后在加载分类。在循环加载。

static void schedule_class_load(Class cls)
{
    schedule_class_load(cls->superclass);
    //将类对象添加到数组,
    add_class_to_loadable_list(cls);
}

/
do {
		while (dable_classes_used > 0) {
            call_class_loads();
        }
        // 2. Call category +loads ONCE
        more_categories = call_category_loads();
} while (loadable_classes_used > 0  ||  more_categories);

注意:runtime load方法调用时(如下代码),并不是利用消息发送函数 objc_msgSend(),而是直接调用loadIMP,所以不会出现只调用分类load方法,也不会覆盖主类的load,都要调用

load_method_t load_method = (load_method_t)classes[i].method;
//地址调用
(*load_method)(cls, SEL_load);

四、initialize

当一个类第一次收到消息时,就会调用类的initialize方法

[Dog alloc];//就会调用Dog的 initialize方法,这时Animal的initialize也会调用
1、如果父类还没有被调用:

那么优先调用父类的initialize方法,在调用子类的initialize方法(如果子类有实现initialize)

2、如果有分类,并且父类还没有被调用:

那么会优先调用父类,在调用分类中的initialize方法。

3、如果主类自己也没有重写initialize方法,分类中没有initialize方法:

那么会调用父类的initialize方法,

注意:这时可能父类会调用多次initialize方法,第一次调用initialize是父类第一次收到消息时调用。然后子类收到消息,但是子类没有实现initialize方法,根据消息机制,会调用父类的initialize方法,此时又会调用父类的initialize方法

loadinitialize
调用时机runtime 加载类,分类时调用类第一次收到消息时调用(父类可能会多次调用)
调用方式通过函数地址直接调用消息机制objc_msgSend()调用
调用顺序父类->子类->分类先初始化父类,此时调用一次父类的initialize,然后初始化子类,调用子类的 initialize(如果子类没有实现,那么又会回到父类的initialize
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值