iOS:Runtime之Category、Extension、load、initialize
大家好,我是OB!
今天来聊聊Runtime和四剑客(Category、Extension、load、initialize)爱恨情仇!
一、category
1、category中的方法会覆盖主类的方法吗
先创建一个Person类和
先看现象
编译时我们可以发现,主类先开始compile,然后才是compile 分类。所以说
从两个角度说:
宏观(现象)角度:分类会覆盖主类的方法!
当分类和主类同时实现
- (void)walk;
方法时,调用结果是调用的分类里面的方法,不管是哪个分类,都优先与主类方法;
微观(源码)角度:分类不会覆盖主类的方法!
虽然优先调用分类的方法。但是主类的方法依然存在。只不过调用优先级比分类的低,当向
Person
发送一个walk
消息时,由于在分类中找到了该方法的实现,所以不再继续,也就不会在调用主类里面的方法了,因此现象上是被覆盖。本质是优先级低,导致不会执行。
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
中的方法还没有添加到类对象,或者元类对象中。只有运行时,动态的添加到类对象或者元类对象;以下摘抄自源码。
可以看出,后编译的category
在while
中先取出来添加到类对象的方法列表中,由于后面还有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的顺序在这里
二、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()
,而是直接调用load
的IMP
,所以不会出现只调用分类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
方法
load | initialize | |
---|---|---|
调用时机 |
runtime 加载类,分类时调用 |
类第一次收到消息时调用(父类可能会多次调用) |
调用方式 | 通过函数地址直接调用 | 消息机制objc_msgSend() 调用 |
调用顺序 | 父类->子类->分类 | 先初始化父类,此时调用一次父类的initialize ,然后初始化子类,调用子类的 initialize (如果子类没有实现,那么又会回到父类的initialize ) |
上一篇: Qt——窗口部件