参考:
Objective-C Runtime Programming Guide
深入Objective-C的动态特性
Objective-C Runtime 运行时之一六:类与对象拾遗
Objective-C Runtime 1小时入门教程
本节将会讲述iOS是如何在系列一中的数据结构的基础上构建消息机制(消息发送到处理的全过程)的,简单来说就是[object|class message];
之类的代码背后发生的一切。
1. 类层次搜索
第一步 - 编译器转化
通过终端命令clang -rewrite-objc xxx.m可以看到xxx.m编译后的xxx.cpp
(C++文件),比对.m文件和.cpp文件,你会发现方括号形式的方法调用基于返回类型的不同被编译器转化成(objc_msgSend系列函数中的某一个)的调用。
通过clang方法也可以分析block的实现,传送门:谈Objective-C block的实现、iOS中block实现的探究
// OC形式:
[receiver messageWithArgs:arg1 and:arg2 …];
// C语言函数及参数说明:
// ◈ receiver => 消息接收者,类型为id,通过其isa指针找到指定类的结构
// ◈ selector => 方法选择器,类型为SEL,用于在类结构的方法分发表中搜索指定名字的方法实现/地址
objc_msgSend(receiver, selector, arg1, arg2, ...)
隐藏参数
在方法的实现中(OC代码的花括号内)有两个隐藏参数可用:self
(receiver)和_cmd
(selector)
注意:
1、在实例方法中,self表示对象;在类方法中,self表示元类对象(即类)。
2、super关键字实际上会被转化成一个objc_super类型的结构体,其值为{self, self.superclass}。
struct objc_super { id receiver; Class class; };
这也意味着在子类都没有重写class方法时,[self class];
和[super class];
最终调用的都是NSObject的class方法实现,而接收者都是self,所以两者返回都的都是self.class;要获取超类,正确的方法是使用[self superclass];
。
第二步 - 追踪继承体系
通过objc_object/objc_class的isa
指针,沿着继承体系在每一个objc_class结构体中:
1、在 cache
中查找指定SEL的实现,失败转2
2、在objc_method_list
中查找指定SEL的实现
获取方法的地址
如果同一个方法实现你需要调用一万次,那么通过NSObject的-methodForSelector:方法绕过动态绑定直接获取方法的实现会提高性能(1万次[a msg] => 一次-methodForSelector:+1万次IMP(a, msg, …)),因为减少了方法实现的搜索次数
setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
setter(target, @selector(setFilled:), YES);
2 消息转发
如果一直到root class都没有定位到SEL的实现,那么转入消息转发过程。
步骤一:动态解析
通过实现resolveInstanceMethod:/resolveClassMethod:方法,我们有机会为该未知消息(SEL)新增一个“处理方法”(IMP)。
- 这意味着在消息转发前,你有机会通过class_addMethod给类动态添加一些方法
- 实际上返回值YES/NO无关紧要,只要你在resovle过程中新增过方法,就会触发
class_getMethodImplementation
,其作用相当于重新启动一次消息发送过程。
步骤二:备用接收者
通过实现** -forwardingTargetForSelector: 方法将消息(SEL)直接转发给另一个对象(备用接收者),也就是在另一个对象**(不能是nil或self)上重启消息发送过程。
步骤三:完整转发
- 通过实现 -methodSignatureForSelector: 提供方法签名(即参数和返回值的类型信息)
- 可通过调用其他类的
+instanceMethodSignatureForSelector:
方法或其他对象的-methodSignatureForSelector:
方法提供 - 也可通过 +signatureWithObjCTypes: 自行生成
- 生成的签名将和原始消息一起打包到一个NSInvocation对象中。
- 通过操作NSInvocation对象的target、selector属性可以方便地转发,甚至转发给另一个对象的另一个需要不同参数的SEL也是可以的
- 通过 -getArgument:atIndex:和-setArgument:atIndex: 可以操作方法调用传入的参数
- 通过 -getReturnValue:和-setReturnValue: 可以直接操作方法invoke后的返回值。
- 实现** -forwardInvocation: **方法
- 通过调用 -invoke方法 重新启动一个消息发送过程。
- 不调用invoke,吞掉这个消息(不做任何处理)
3 转发的功能
转发和多继承
转发模拟了继承,所以可以用来为Objc程序提供类似多继承的功能。转发和多继承的区别如下:
◈ 多继承是将许多功能combine到一个对象中;
◈ 转发则将功能分解到多个对象,并一种对消息发送者透明的方式将它们关联起来;
代理/替代对象
场景描述:当你有一个对象,这个对象的设置由于需要处理大量数据非常耗时,所以更倾向于懒加载——在真正需要或系统空闲的时候来进行加载,这时你需要一个占位对象来使得应用的其他部分正常工作,这个占位对象的工作如下:
◈ 获取关于待加载数据的描述信息
◈ 转发消息时检测对象是否创建并已加载完数据,据此决定创建对象、丢弃消息或转发消息。
转发和继承
以下方法只考虑类继承体系(不含转发链);如需要对象表现得和继承一样,重写它们并把转发算法包括进来:
◈ -respondsToSelector: & +instancesRespondToSelector:
◈ -isKindOfClass: & -isMemberOfClass:
◈ -conformsToProtocol:
总结:
- 消息机制:继承体系搜索 -> 消息转发 ( 动态解析-> 快速转发 -> 完整转发 )
- 转发和继承(-respondsToSelector:等)、多继承、代理