Runtime 学习笔记
要点一:对象,类,元类关系
image.png每个对象都有一个类对象
每个类对象有一个元类对象
每一个对象都有一个isa指针,这个指针指向它的类对象
每一个类对象都有一个isa指针和一个super_class指针,isa指针指向它的元类对象,super_class指针指向它的父类
要点二:元素存储位置
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
1.类对象:类对象存储的是关于实例对象的信息(属性,实例方法等)
2.元类对象:元类对象存储的是关于类的信息(类的版本,名字,类方法等)
3.类对象(class object)和元类对象(metaclass object)的定义都是objc_class结构
4.根类NSObject的类对象没有父类,根元类继承于类对象,根元类的isa指向自己.
5.所有类的类对象的继承关系就是元类对象的继承关系。
要点三:方法调用会被缓存
调用的方法会被缓存。
一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。
要点四:分类的动态插入
typedef struct category_t {
const char *name; //类的名字
classref_t cls; //类
struct method_list_t *instanceMethods; //category中所有给类添加的实例方法的列表
struct method_list_t *classMethods; //category中所有添加的类方法的列表
struct protocol_list_t *protocols; //category实现的所有协议的列表
struct property_list_t *instanceProperties; //category中添加的所有属性
} category_t;
从category的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。
假如有一个名称为test的实例方法,实例方法在对象的类对象实例方法列表中,当我们调用[self test]这个方法时,会先去该对象的类对象的实例方法列表中去找,如果没找到,继续去类对象的父类中去找。
类对象的实例方法是在编译的时候添加到方法列表中的,分类的实例方法是在运行时动态添加到类对象的方法列表中的,[self test] 找实例方法的时候是按顺序查找的,因为分类的实例方法是后来添加的,所以肯定在方法列表的前面,所以就先调用分类的方法。
两个分类有相同的实例方法的时候,就看动态添加的时候,是谁后添加到方法列表中的了,谁后添加,谁就在前面,就调用谁。
要点五:OC函数不能像C++一样被重载
因为OC是根据方法名称生成的SEL来查找方法的实现imp的,如果再方法列表中有两个名称一样的方法,那么对象将无法分辨到底现在调用的是那个方法,相同的方法只能对应一个SEL。