面试题0:Category的使用场合是什么?
可以添加成员属性、实例方法、类方法、协议
面试题1:Category的实现原理
编译结束时候,每个Category会转成struct _category_t这样的结构体,
结构体中,存放着对象方法列表、类方法列表、协议列表、属性列表
在运行阶段,通过runtime将所有Category,就是多个struct _category_t中的所有列表
合并到class、meta-class信息中。并非真实覆盖,而且顺序提前了。会被优先调用。
面试题2:Category和Extention的区别是什么
Extention只是相当于在“编译”的时候把Extention中的东西合并到.m文件而已。
或者是增加一些属性、方法的私有化。而且不能有重复的属性和方法。
面试题3:Category中有load方法吗?load方法是什么时候调用的?load方法能继承吗?
Category也有load方法。load方法是在Runtime把类、分类加载进内存的时候调用。
是可以继承的。Load方法是系统调用的。没见过谁会主动调用load方法。而且继承这种技术,是存在于子类于父类之间才会用到的技术。
面试题4:load、initialize方法的区别是什么?他们在Category中的调用顺序?以及出现继承时,他们之间的调用过程?
面试题5:Category能否添加成员变量?如果可以,如何给Category添加成员变量?
例子:
比如有一个Person类里面有-run方法和+run1方法。
Person(Eat)分类中有-eat方法和+eat1方法。
Person(Drink)分类中有-drink方法和+drink1方法。
那么,-eat和-drink都会放到统一的Person的类对象中
+eat1和+drink1方法都会统一放在Person的meta-class中。
其实是通过runtime在程序运行的时候进行合并的,并非编译的时候合并的。
// 分类的底层结构
struct _category_t {
const char *name; // 类名(比如属于Person类)
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最终都是转成_category_t这样的结构。当运行的时候,利用runtime方法将结构体里面的东西合并到class对象(和meta-class对象)里面去
合并的时候,先扩容,然后把原来class中的,meta-class中的的挪到后面,前面腾出所有分类数量的位置。然后再把分类的插进来。
所以,优先调用分类(因为分类的会插在前面),如果两个分类都有run方法,那么就取决于编译顺序了。white(i- -),最后编译的分类,排在最前面,最先被调用。编译顺序,看Build Phases。分类之间,越靠后编译,越靠前被调用。
如果Person类和两个分类中都有run方法,其实不会覆盖(假覆盖),只不过是顺序超前了,而且是优先调用分类里面的方法的。
【+ load方法原理】:
类、分类都可以重写load方法。都有load方法,当runtime加载类和分类的时候,就会调用他们各自的load方法
即使我没有import某一个类,也会调用它的load方法。将他们载进内存
有一个疑问:分类的方法不是会跟class或者meta-class合并再一起的吗?我们尝试了打印,meta-class中的所有方法,确实是发现+load方法是合并再一起的。而且按道理来说会先调用分类的+load方法。
需要查看源码来解释:是系统帮忙调用的,系统直接找到类的load方法的地址,直接调用。系统先调用类的+load方法,(所有类的load方法调用完了)然后再调用分类的+load方法。
而不是像我们【Person load】方法由运行时合并完,去合并后的数组里面按顺序查找。(我们这种方式的查找,相当于objc_msgSend
每个类、分类的+load方法,在程序运行过程中,只调用一次。会在runtime加载类、分类的时候调用(跟你是否import无关)
那么load方法的调用顺序呢?(如果Student继承自Person,那么总是先调用Person(父类)的load方法,而且,类的load方法总是先于所有的Category的load方法)
如果没有继承关系的类之间,调用顺序就跟编译先后有关了。编译先的会先被调用load方法。分类的load方法调用顺序就是按照编译顺序了(编译顺序就是看Build Phases)
【+initialize方法的原理:】
+initialize方法会在类第一次接收到消息的时候调用,是使用msgSend方法,所以也是会如果有分类多个重写了,会优先调用分类的。
+initialize方法也是只调用一次。
调用顺序:会先调用父类的+initialize,再调用子类的+initialize。
比如Student继承自Person(两个类中都重写+initialize方法)
场景1:
【Person alloc】 // 调用Person的+initialize方法(当然如果有Category,会调用Category里面的)
【Student alloc】 // 调用Student的+initialize方法(当然如果有Category,会调用Category里面的)
场景2:
【Student alloc】 // 先调用Person的+initialize方法(当然如果有Category,会调用Category里面的),然后调用Student的+initialize方法
【Person alloc】 // 什么都不调用了,因为Student以及用完了
猜测,objc_msgSend方面内部会判断是否第一次调用,然后再调用initialize方法
initialize可以重写,第一次使用这个类的时候做一些事情,那么我就重写做一些事情
initialize是给开发者用的。