什么是Runtime
- OC语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。运行时系统就像一个操作系统一样:它让所有的工作可以正常的运行。这个运行时系统即Objc Runtime。Objc Runtime基本上是用C和汇编写的,这个库使得C语言有了面向对象的能力。
- Runtime其实有两个版本: “
modern
” 和 “legacy
”。我们现在用的Objective-C 2.0
采用的是现行 (Modern
) 版的 Runtime 系统,只能运行在 iOS 和 macOS 10.5 之后的 64 位程序中。而 macOS 较老的32位程序仍采用 Objective-C 1 中的(早期)Legacy
版本的Runtime
系统。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版就不需要。
- runtime(简称运行时),是一套纯C(C和汇编)编写的API。而OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制 objc_msgSend。
- 对于 C 语言,函数的调用在编译的时候会决定调用哪个函数。
- 运行时机制原理:OC的函数调用称为消息发送,属于动态调用过程。在编译的时候 并不能决定真正调用哪个函数,只有在真 正运行的时候才会根据函数的名称找到对应的函数来调用。Runtime将数据类型的确定由编译时推迟到了运行时。
- 事实证明:在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错,只有当运行的时候才会报错,这是因为OC是运行时动态调用的。而C语言调用未实现的函数就会报错。
Runtime的组成
//实例对象
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY; //实例对象的isa指针指向他的类对象
};
//类对象
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;//isa指针指向Meta Class
#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;
// 方法缓存,对象接到一个消息会根据isa指针查找消息对象,这时会在method Lists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} 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;
} OBJC2_UNAVAILABLE;
//方法
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;//方法名
char *method_types OBJC2_UNAVAILABLE;//方法类型
IMP method_imp OBJC2_UNAVAILABLE;//指向方法实现的指针
}
//Category
struct category_t {
const char *name; //是指 class_name 而不是 category_name。
classref_t cls; //要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对 应到对应的类对象.
struct method_list_t *instanceMethods; //category中所有给类添加的实例方法的列表。
struct method_list_t *classMethods; //category中所有添加的类方法的列表。
struct protocol_list_t *protocols; //category实现的所有协议的列表。
struct property_list_t *instanceProperties; //表示Category里所有的properties,这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因
};
Runtime消息传递
- 实例对象调用方法后,底层调用
[objc performSelector:@selector(SEL)];
方法,编译器将代码转化为objc_msgSend(receiver, selector)
。 - 在
objc_msgSend
函数中,首先通过objc
的isa
指针找到objc
对应的class
,在class
中先去cache
中通过SEL
查找对应函数的method
,如果找到则通过method
中的函数指针跳转到对应的函数中去执行。 - 如果在
cacha
中未找到,再去methodList
中查找,如果能找到,则将method
加入到cache
中,以方便下次查找,并通过method
中的函数指针跳转到对应的函数中去执行。 - 如果在
methodlist
中未找到,则去superClass
中去查找,如果能找到,则将method
加入到cache中,以方便下次查找,并通过method
中的函数指针跳转到对应的函数中去执行。
- objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,即:objc_msgSend(receiver, selector)。如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:
- objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:(实例方法和类方法),让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。
- 如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。
- 这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。
一个完整转发的例子:
#import "ViewController.h"
#import "objc/runtime.h"
@interface Person: NSObject
@end
@implementation Person
- (void)foo {
NSLog(@"Doing foo");//Person的foo函数
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//执行foo函数
[self performSelector:@selector(foo)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES;//返回YES,进入下一步转发
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil;//返回nil,进入下一步转发
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];//签名,进入forwardInvocation
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
Person *p = [Person new];
if([p respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p];
}
else {
[self doesNotRecognizeSelector:sel];
}
}
@end
Objective-C type encodings
Objective-C type encodings
Code | Meaning |
| A |
| An |
| A |
| A
|
| A |
| An |
| An |
| An |
| An |
| An |
| A |
| A |
| A C++ |
| A |
| A character string ( |
| An object (whether statically typed or typed |
| A class object ( |
| A method selector ( |
[array type] | An array |
{name=type...} | A structure |
(name=type...) | A union |
| A bit field of num bits |
| A pointer to type |
| An unknown type (among other things, this code is used for function pointers) |