一 什么是runtime
Objective-C是面向运行时的语言(runtimeoriented language),就是说它会尽可能的把编译和链接时要执行的逻辑延迟到运行时。这就给了你很大的灵活性,你可以按需要把消息重定向给合适的对象,你甚至可以交换方法的实现,等等
我们平时编写的 OC 代码,底层都是基于它来实现的。比如:
[receiver message];
//底层运行时会被编译器转化为:
objc_msgSend(receiver, selector)
//如果其还有参数比如:
[receiver message:(id)arg...];
//底层运行时会被编译器转化为:
objc_msgSend(receiver, selector, arg1, arg2, ...)
有很多类和成员变量在我们编译的时是不知道的,而在运行时,我们所编写的代码会转换成完整的确定的代码运行。
因此,编译器是不够的,我们还需要一个运行时系统(Runtimesystem)来处理编译后的代码。
Runtime 基本是用 C 和汇编写的。
二、一个对象调用一个方法的全过程
举个栗子 :
1.我要找小P借本书,我通过指向小P位置的坐标(指针)找到她 ,他说书(方法)是她们部门(类)统一的 财产,要找组织上借(类),小P(通过isa指针)告诉我组织(类)的位置,并找到组织(类)的书架(methodLists方法列表),我用我的想要的书名(SEL 方法编号)在书架中寻找到那本书(IMP是函数指针,IMP中存了方法的地址),最后借到这本书。
2.cache 我看完这本书后,把书还给了小P ,小P默默的把这本书放在了书架的特定位置,我再次跟她借这本书的时候,就不用耗费性能再去书架上找了,这个叫Class 的cache,所以在我借书的时候都会先从书架的特定位置缓存里拿,拿不到才会去methodlist里去找。
3.如果小P的组织中的缓存和Methodist中找不到这本书,那么我会到组织的上级组织 (类的父类)中的缓存和Methodlist中去找 ,找不到就再到父类的父类中去找 。一直到最高层的父类 NSObject为止。
4.如果一直没有找到这本书,
step 1.首先我先问小P,可不可以帮我买本新书。买就直接拿走那本新书;(动态方法解析,动态添加方法)
step 2.不给买我会看看原来的计划中有没有备案可以找到别人借书。(消息备用接受者)
step 3. step 2不成功,我将看看原计划中是否有处理找不到书这种情况的备案。有备案调用备案方法,没有抛出异常。(启用完整的消息转发机制)
Class
typedef struct objc_class *Class;
Class
其实是指向 objc_class
结构体的指针。objc_class
的数据结构如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; isa指针 (指向对象的类)
#if !__OBJC2__
Class super_class 父类
const char *name 类名
long version
long info
long instance_size
struct objc_ivar_list *ivars 成员变量
struct objc_method_list **methodLists 方法列表 dispatch table SEL 去寻找IMP
OBJC2_UNAVAILABLE;
struct objc_cache *cache 缓存
struct objc_protocol_list *protocols 协议列表
#endif
} OBJC2_UNAVAILABLE;
从 objc_class
可以看到,一个运行时类中关联了它的父类指针、类名、成员变量、方法、缓存以及附属的协议。
其中 objc_ivar_list
和 objc_method_list
分别是成员变量列表和方法列表:
// 成员变量列表
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
// 方法列表
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
objc_ivar_list
结构体用来存储成员变量的列表,而 objc_ivar
则是存储了单个成员变量的信息;
objc_method_list
结构体存储着方法数组的列表,而单个方法的信息则由 objc_method
结构体存储。
方法列表:
ps: 1.(IMP 和SEL的区别 SEL:类成员方法的指针,但不同于C语言中的函数指针,函数指针直接保存了方法的地址,但SEL只是方法编号,IMP函数指针保存了方法地址)
2.IMP SEL 的详细解释 IOS SEL 原理。
3. 我们可以动态修改 *methodList
的值来添加成员方法,这也是 Category 实现的原理
值得注意的时,objc_class
中也有一个 isa 指针,这说明 Objc 类本身也是一个对象。为了处理类和对象的关系,Runtime 库创建了一种叫做 Meta Class(元类) 的东西,类对象所属的类就叫做元类。Meta Class 表述了类对象本身所具备的元数据。
我们所熟悉的类方法,就源自于 Meta Class。我们可以理解为类方法就是类对象的实例方法。每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类。
当你发出一个类似 [NSObjectalloc](
类方法)
的消息时,实际上,这个消息被发送给了一个类对象(Class Object),这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类(Root Meta Class)的实例。所有元类的 isa 指针最终都指向根元类。
所以当 [NSObject alloc]
这条消息发送给类对象的时候,运行时代码 objc_msgSend()
会去它元类中查找能够响应消息的方法实现,如果找到了,就会对这个类对象执行方法调用
上图实线是 super_class 指针,虚线时 isa 指针。而根元类的父类是 NSObject,isa指向了自己。而 NSObject 没有父类。
消息传递是如何工作的?
在Objective-C中调用方法最终会翻译成调用方法实现的函数指针,并传递给这个方法实现一个对象指针、一个选择器和一组函数参数。每个Objective-C消息表达式都会转化为对objc_msgSend的调用,看下objc_msgSend的工作方式:
- 1 首先去该类的方法
cache
中查找,如果找到了就返回它; - 2 如果没有找到,就去该类的方法列表中查找。如果在该类的方法列表中找到了,则将
IMP
返回,并将 它加入cache
中缓存起来。根据最近使用原则,这个方法再次调用的可能性很大,缓存起来可以节省下次 调用再次查找的开销。 - 3 如果在该类的方法列表中没找到对应的
IMP
,在通过该类结构中的super_class
指针在其父类结构的方法列表中去查找,直到在某个父类的方法列表中找到对应的IMP
,返回它,并加入cache
中; - 4 如果在自身以及所有父类的方法列表中都没有找到对应的
IMP
,则看是不是可以进行动态方法决议(后 面有专文讲述这个话题); - 5 如果动态方法决议没能解决问题,进入下面要讲的消息转发流程。便利函数:我们可以通过
NSObject
的一些方法获取运行时信息或动态执行一些消息.
消息转发机制
当我们像一个对象发送消息[Receiver message],Receiver没有实现该消息,即[Receiver respondsToSelector:SEL]返回为NO情况下,其实系统不会立刻出现cash,这时Runtime system会对message进行转发。转发之后,如果该消息依然没有被执行就会出现Cash!Runtime System为我们提供了三种解决这种给对象发送没有实现消息方案。消息转发机制基本上分为三个步骤:
1. 动态方法解析
2. 备用接收者
如果在动态方法解析无法处理消息,则Runtime会继续调以下方法:
- (id)forwardingTargetForSelector:(SEL)aSelector
使用这个方法通常是在对象内部,可能还有一系列其它对象能处理该消息,我们便可借这些对象来处理消息并返回,这样在对象外部看来,还是由该对象亲自处理了这一消息。
3. 完整转发
如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。
此时会调用以下方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation
由于NSInvocation的初始化需要有一个方法签名NSMethodSignature,所以我们还需要实现下面的方法,该方法的返回值用于初始化NSInvocation的。
-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
4.crash 如果以上都没有实现 系统会报错