##Category的本质一
链接:https://www.jianshu.com/p/da463f413de7
总结:
- 通过runtime加载某个类的所有Category数据。
- 把所有Category的方法,属性,协议数据合并到一个大数组中,后面参与斌编译的Category数据,会在数组的前面。
- 将合并后的分类数据(方法,属性,协议),插入到类原来数据的前面。
分类中的对象方法和类方法最终会合并到类中,分类中的对象方法合并到类的类对象中,分类中的类方法合并到类的元类对象中。那么这个合并是什么时候发生的呢?是在编译器编译器就帮我们合并好了吗?实际上是在运行期,进行的合并。整个合并的过程是通过runtime进行实现的,最后合并到类对象的方法列表中或者元对象的方法列表中。
下面我们通过将Objective-c的代码转化为c++的源码窥探一下Category的底层结构。我们在命令行进入到存放Person+Test.m这个文件的文件夹中,然后在命令行输入clang -rewrite-objc Person+Test.m,这样Person+Test.m这个文件就被转化为了c++的源码Person+Test.cpp。
合并的时候并不是覆盖,只是在类对象的方法列表中,分类的方法数组被copy放到了列表中原有方法的前面。查找方法的时候总是先查找到分类中最后参与编译的方法。(在Compile Sources靠后的就是后编译的)
##Category的本质<二>load,initialize方法
链接:https://www.jianshu.com/p/b5492c40fe8f
面试题1:Category中有load方法吗?load方法是什么时候调用?
面试题2:load,initialize的区别是什么?它们在Category中的调用顺序以及出现继承时它们之间的调用过程是怎么样的?
那么这篇文章主要就是回答这两个问题。
有load方法。因为load方法的调用并不是objc_msgSend机制,它是直接找到类的load方法的地址,然后调用类的load方法,然后再找到分类的load方法的地址,再去调用它。
1.先调用类的load方法
调用子类的load方法之前会先调用父类的load方法
2.再调用分类的load方法
按照编译先后顺序,先编译,先调用
load方法什么时候调用?
load方法是在runtime加载类和分类的时候调用。
initialize在类第一次接收到消息时调用,也就是objc_msgSend()。
+initialize的调用过程:
1查看本类的initialize方法有没有实现过,如果已经实现过就返回,不再实现。
2.如果本类没有实现过initialize方法,那么就去递归查看该类的父类有没有实现过initialize方法,如果没有实现就去实现,最后实现本类的initialize方法。并且initialize方法是通过objc_msgSend()实现的。
+initialize和+load的一个很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点:
如果子类没有实现+initialize方法,会调用父类的+initialize(所以父类的+initialize方法可能会被调用多次)
如果分类实现了+initialize,会覆盖类本身的+initialize调用。
下面我们把Student类及其分类中的+initialize这个方法的实现去掉,然后增加一个Teacher类继承自Person类。然后我们给Student类和Teacher类都发送alloc消息:
[Student alloc];
[Teacher alloc];
这个时候也就是只有Person类及其分类实现了+initialize方法。那么打印结果会是怎样呢?
2018-07-25 21:47:59.899995+0800 interview - Category[20981:582224] Person (Test2) + initialize
2018-07-25 21:47:59.900112+0800 interview - Category[20981:582224] Person (Test2) + initialize
2018-07-25 21:47:59.900240+0800 interview - Category[20981:582224] Person (Test2) + initialize
这里Person类的+initialize方法竟然被调用了三次,这多少有些出乎意外吧。下面我们来分析一下。
BOOL studentInitialized = NO;
BOOL personinitialized = NO;
BOOL teacherInitialized = NO;
[Student alloc];
//判断Student类是否初始化了,这里Student类还没有被初始化,所以进入条件语句。
if(!studentInitialized){
//判断Student类的父类Person类是否初始化了
if(!personinitialized){
//这里Person类还没有初始化,就利用objc_msgSend调用initialize方法
objc_msgSend([Person class], @selector(initialize));
//变更Person类是否初始化的状态
personinitialized = YES;
}
//利用objc_msgSend调用Student的initialize方法
objc_msgSend([Student class], @selector(initialize));
//变更Student是否初始化的状态
studentInitialized = YES
}
[Teacher alloc];
//判断Teacher类是否已经初始化了,这里Teacher类还没有初始化,进入条件语句
if(!teacherInitialized){
//判断其父类Person类是否初始化了,这里父类已经初始化了,所以不会进入这个条件语句
if(!personinitialized){
objc_msgSend([Person class], @selector(initialize));
personinitialized = YES;
}
//利用objc_msgSend调用Teacher类的initialize方法
objc_msgSend([Teacher class], @selector(initialize));
//变更状态
teacherInitialized = YES;
}
上面列出来的是调用initialize的伪代码,下面再详细说明这个过程:
1.Student类收到alloc消息,开始着手准备调用initialize方法。首先判断自己有没有初始化过。
2.判断自己没有初始化过,所以就去找自己的父类Person类,看Person类有没有初始化过,发现Person类也没有初始化过,且Person类也没有父类,所以对Person类使用objc_msgSend([Person class], @selector(initialize))调用Person类的initialize方法。这是第一次调用Person类的initialize方法。
3.父类处理完后,再通过objc_msgSend([Student class], @selector(initialize));调用Student类的initialize方法,但是由于Student类没有实现initialize方法,所以通过其superclass指针找到父类Person类,然后调用了Person类的initialize实现。这是第二次调用Person类的initialize方法。
4.Teacher类收到alloc方法,开始准备调用initialize方法。首先判断自己有没有被初始化过。
5.判断自己没有被初始化过后,又开始判断其父类Person类有没有被初始化过,刚刚父类Person类已经被初始化过。
6.于是通过objc_msgSend([Teacher class], @selector(initialize))调用Teacher类的initialize方法。但是由于Teacher类没有实现initialize方法,所以只能通过superclass指针去查找父类有没有实现initialize方法,发现父类Person类实现了initialize方法,于是调用父类的initialize方法。这是第三次调用Person类的initialize方法。