一、本质
[receiver message]不是一个简单地方法调用,而是在编译阶段被编译器转化为
objc_msgSend(Class receiver,SEL selector, arg1, arg2, ...)
,只是在编译阶段确定了要向receiver发送message这条消息,而receiver如何响应这条消息,要看运行时来决定,消息的receiver能够找到对应的selector,那么就相当于直接执行了receiver这个对象的特定方法;否则,消息要么被转发,或是临时向receiver动态添加这个selector对应的实现内容,要么就干脆崩溃掉。
NSObject还有些方法能在运行时获得类的信息,并检查一些特性,比如class返回对象的类;isKindOfClass:和isMemberOfClass:则检查对象是否在指定的类继承体系中;respondsToSelector:检查对象能否响应指定的消息;conformsToProtocol:检查对象是否实现了指定协议类的方法;methodForSelector:则返回指定方法实现的地址。
二、Class结构[objc_msgSend(Class receiver,SEL selector, arg1, arg2, …)之Receiver]
@interface NSObject <NSObject> {
Class isa;
}
有一个Class类型的isa属性,typedef struct objc_class *Class,所以Class是一个objc_class结构类型的指针;
struct objc_class {
struct objc_class *isa; //指向该对象所属类型的类型对象(Class Object),而类的isa指针指向它的metaclass.
struct objc_class *super_class; //指向父类,如果该类已经是最顶层的根类(如 NSObject 或 NSProxy),那么 super_class 就为 NULL.
const charchar *name; //类名称
long version; //类的版本信息
long info; //运行期使用的标志位,比如0x1(CLS_CLASS)表示该类为普通class,0x2(CLS_META)表示该类为metaclass
long instance_size; //实例大小,即内存所占空间
struct objc_ivar_list *ivars; //指向成员变量列表的指针
struct objc_method_list **methodLists; //根据info标志位的不同可能指向不同,比如可能指向实例方法列表,或者指向类方法列表
struct objc_cache *cache; //因为Objective-C的消息转发需要查找dispatch table甚至可能需要遍历继承体系,所以缓存最近使用的方法。
struct objc_protocol_list *protocols; //类需要遵守的协议
}
isa&superclass
每一个对象本质上都是一个类的实例。其中类定义了成员变量和成员方法的列表。对象通过对象的isa指针指向类。
每一个类本质上都是一个对象,类其实是元类(meteClass)的实例。元类定义了类方法的列表。类通过类的isa指针指向元类。
所有的元类最终继承一个根元类,根元类isa指针指向本身,形成一个封闭的内循环
superclass指向该类的父类, 如果该类已经是最顶层的根类(如 NSObject 或 NSProxy),那么 super_class 就为 NULL.
上图实线是 super_class 指针,虚线是isa指针。 有趣的是根元类的超类是NSObject,而isa指向了自己,而NSObject的超类为nil,也就是它没有超类。
需要深刻理解 [self class] 与 object_getClass(self) 甚至 object_getClass([self class]) 的关系,其实并不难,重点在于 self 的类型:
当 self 为实例对象时,[self class] 与 object_getClass(self) 等价,因为前者会调用后者。object_getClass([self class]) 得到元类。
当 self 为类对象时,[self class] 返回值为自身,还是 self。object_getClass(self) 与 object_getClass([self class]) 等价。
objc_ivar_list 成员变量的数组,成员变量生成后变不能修改
struct objc_ivar_list {
int ivar_count; /* variable length structure */
struct objc_ivar ivar_list[1];
}
objc_method_list 方法列表指针,存储着objc_method列表,可以动态修改方法列表的值来添加成员方法
struct objc_method_list {
struct objc_method_list *obsolete;
int method_count; /* variable length structure */
struct objc_method method_list[1];
}
objc_cache 指向最近使用的方法.用于方法调用的优化.
struct objc_cache {
unsigned int mask /* total = mask + 1 */;
unsigned int occupied;
Method buckets[1];
};
protocols 协议的数组指针
struct objc_protocol_list {
struct objc_protocol_list *next;
long count;
Protocol *list[1];
};
三、SEL[objc_msgSend(Class receiver,SEL selector, arg1, arg2, …)之selector]
Seloctor:方法选择器,其实它就是个映射到方法的C字符串,你可以用 Objc 编译器命令@selector()或者 Runtime 系统的sel_registerName函数来获得一个SEL类型的方法选择器。SEL类型的数据结构是SEL:typedef struct objc_selector *SEL;每个selector会对应一个IMP来实现函数(methodForSelector可以获取Selector对应的IMP);
Method:一种代表类中的某个方法的类型。
在Class的objc_method_list里有一个objc_method列表
struct objc_method {
SEL method_name
char *method_types 存储着方法的参数类型和返回值类型
IMP method_imp 指向了方法的实现,本质上是一个函数指针
}
IMP:具体的方法的地址,IMP 是一个函数指针,这个被指向的函数包含一个接收消息的对象id(self 指针), 调用方法的选标 SEL (方法名),以及不定个数的方法参数,并返回一个id。也就是说 IMP 是消息最终调用的执行代码,是方法真正的实现代码 class_getMethodImplementation(class, SEL)。
1、methodForSelector->Method
2、method_getImplementation->IMP
四、消息发送机制:
伪代码:
id objc_msgSend(id self, SEL _cmd, ...) {
Class class = object_getClass(self);
IMP imp = class_getMethodImplementation(class, _cmd);
return imp ? imp(self, _cmd, ...) : 0;
}
receiver查找:
1、判断接受者receiver,也就是第一个参数self,是否为空? 如果为空,调用nil-handler, 默认为什么都不做。
方法查找
2、检查class的方法调用cache,是否调用过此方法。找到了则分发,否则
3、用objc-class.mm中_class_lookupMethodAndLoadCache3
检查从本class的method list寻找selector,如果找到,填充到缓存中,并返回selector。否则
4、寻找父类的method list,并依次往上寻找(会找到NSObject),直到找到selector,填充到缓存中,并返回selector,否则
动态解析,这里动态添加方法
5、如果找到可以动态resolve为一个selector(调用 resolveInstanceMethod:
和 resolveClassMethod:
方法添加实例方法实现和类方法实现。),不缓存,方法返回,否则
消息快速转发,这里将消息分配给其他对象处理
6、消息转发机制执行前,Runtime 系统允许我们替换消息的接收者为其他对象。通过 - (id)forwardingTargetForSelector:(SEL)aSelector
方法。
- (id)forwardingTargetForSelector:(SEL)sel { return _otherObject; }
因为这一步不会创建任何新的对象,但下一步转发会创建一个 NSInvocation 对象,所以相对更快点。
消息普通转发
7、首先runtime发送methodSignatureForSelector:
消息
生成Selector对应的方法签名,即参数与返回值的类型信息。
如果有方法签名返回,runtime则根据方法签名创建描述该消息的NSInvocation(NSInvocation 实际上就是对一个消息的描述,包括selector 以及参数等信息),向当前对象发送forwardInvocation:
消息,以创建的NSInvocation对象作为参数;
若methodSignatureForSelector:
无方法签名返回,则向当前对象发送doesNotRecognizeSelector:
消息,程序抛出异常退出。
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL sel = invocation.selector;
if([someObject respondsToSelector:sel]) {
[invocation invokeWithTarget:someObject];
}
else { //或者崩掉
[self doesNotRecognizeSelector:sel];
}
}
与快速转发二者区别:快速转发不用专门生成NSInvocation,快速消耗少,普通转发可以转发给多个对象。
五、动态添加属性
category可以动态添加方法,借助Runtime还可以添加属性,但是不能添加成员变量,Category可以通过runtime.h中objc_getAssociatedObject / objc_setAssociatedObject来访问和生成关联对象。通过这种方法来模拟生成属性,但是与对象还是有点区别,因为对象属性会编译器自动生成setter和getter方法,会默认给你生成一个以下划线开头的成员变量,而category不手动去生成setter和getter的话,会报错。
1、不能添加成员变量,会报错
这个函数只能在“构建一个类的过程中”调用。一旦完成类定义,就不能再添加成员变量了
六、KVC与KVO
1、KVC
KVC运用了isa-swizzing技术。isa-swizzing就是类型混合指针机制。KVC通过isa-swizzing实现其内部查找定位。isa指针(is kind of 的意思)指向维护分发表的对象的类,该分发表实际上包含了指向实现类中的方法的指针和其他数据。
比如说如下的一行KVC代码:
[site setValue:@"sitename" forKey:@"name"];
会被编译器处理成
SEL sel = sel_get_uid(setValue:forKey);
IMP method = objc_msg_loopup(site->isa,sel);
method(site,sel,@"sitename",@"name");
每个类都有一张方法表,是一个hash表,值是还书指针IMP,SEL的名称就是查表时所用的键。 SEL数据类型:查找方法表时所用的键。定义成char*,实质上可以理解成int值。 IMP数据类型:他其实就是一个编译器内部实现时候的函数指针。当Objective-C编译器去处理实现一个方法的时候,就会指向一个IMP对象,这个对象是C语言表述的类型。
2、KVO
当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法。
派生类在被重写的 setter 方法实现真正的通知机制,就如前面手动实现键值观察那样。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。
同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。
简单而言:在被观察时,生成派生类,对于观察属性重写setter方法,然后在valuewillchange方法和valuesdidchanged方法里发出通知
1、当一个object有观察者时,动态创建这个object的类的子类
2、对于每个被观察的property,重写其set方法
3、在重写的set方法中调用- willChangeValueForKey:和- didChangeValueForKey:通知观察者
4、当一个property没有观察者时,删除重写的方法
5、当没有observer观察任何一个property时,删除动态创建的子类
七、Method Swizzling原理
在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法(Hook)挂钩的目的。
每个类都有一个方法列表,存放着selector的名字和IMP方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。
我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,
我们可以利用 method_setImplementation 来直接设置某个方法的IMP
八、利用Runtime可以作用的一些方法:
class_getName:获取类名
class_getSuperclass:获取父类
class_getInstanceSize:获取实例大小
class_getInstanceVariable:获取实例成员变量
class_getClassVariable:获取类成员变量
class_getProperty:获得属性
class_getInstanceMethod:获得实例方法
class_getClassMethod:获得类方法class_getMethodImplementation:获得IMP
class_copyIvarList:获取成员变量列表
class_copyMethodList:获取方法列表
class_copyProtocolList:获取协议列表
class_addIvar:添加成员变量(添加成员变量只能在运行时创建的类,且不能为元类)
class_addProperty:添加属性
class_addMethod:添加方法
class_addProtocol:添加协议
class_replaceProperty:替换属性的信息(如果没有原属性会新建一个属性)
class_replaceMethod:替代方法的实现
class_respondsToSelector:查看类是否相应指定方法
class_isMetaClass:查看类是否为元类
class_conformsToProtocol:查看类是否遵循指定协议
object_getInstanceVariable:获取实例的成员变量
object_getIvar:获取成员变量的值
object_getClassName:获取指定对象的类名
object_getClass:获取指定对象的类
objc_getMetaClass:获取指定类的元类
object_copy:拷贝指定对象
objc_getProtocol:获取指定名字的协议
object_setInstanceVariable:设置指定实例指定名称的成员变量的值
object_setIvar:设置指定对象的指定的成员变量的值
objc_setAssociatedObject:设置关联对象的值
objc_getAssociatedObject:获取关联对象的值
objc_removeAssociatedObjects:移除关联对象
ivar_getName:获取成员变量名
ivar_getTypeEncoding:获取成员变量类型编码
ivar_getOffset:获取成员变量的偏移量
property_getName:获取属性名
property_copyAttributeValue:获取属性中指定的特性
method_invoke:调用指定方法的实现
method_getName:获取方法名
method_getImplementation:返回方法的实现