runtime是oc中一个比较底层的纯c语言库,包含了很多底层c语言的api
runtime其实和我们编程密切相关,平时编写的oc代码中,在程序运行时,其实最终都是转成runtime的c语言代码
可以这么理解,oc的底层实际上是在调用一个个c函数,如何找到这些c函数的工作,就是由runtime去负责了
要理解runtime,首先要理解类的结构
类的本质是什么? 实际上就是一个类似结构体的玩意,那么类里面有什么内容呢,以下一张图就能够很清晰的理解
isa就是“is a”,对于所有继承了NSObject的类其对象也都有一个isa指针。这个isa指针指向关于这个对象所属的类的定义。
任何直接或间接继承了NSObject的类,它的实例对象(instacne objec)中都有一个isa指针,指向它的类对象(class object)。这个类对象(class object)中存储了关于这个实例对象(instace object)所属的类的定义的一切:包括变量,方法,遵守的协议等等。
以下这图可以很清楚的表述上面的关系
简单来说,一个类的对象的isa指针会指向这个类,这个类也有一个isa指针会指向这个类的元祖类(meta class)。
一个类以及其元祖类(meta class) 都会有一个super class 的指针指向其父类,一直到根类(NSObject),NSObject的元祖类的super class 会指回到NSObject类,至于NSObject是没有super class 的,其super class 指针会指向nil
至于类和元祖类是什么关系呢? 首先,他们都是对象,根本上都是objc_class对象,因此也有类对象和元祖类对象的一说。区别在于,类对象中包含了这个类的实例变量,实例方法的定义,而元祖类对象中包含类的方法的定义。
搞清楚对象和类的这层关系之后,要理解起来就轻松多了,你可以理解成isa 和 super class 就是一个门牌号或者路标,runtime就是根据这些路标去找到相应的静态方法和变量的。
-------------------------------------- 华丽的分割线 ----------------------------------
搞清楚指针之后,之后就看重点了
ivars(成员变量列表)
这个属性 objc_ivar_list 结构体类型,其实就是一个链表,存储多个objc_ivar,而objc_ivar又是一个结构体,用来存储类的单个成员变量的信息
简单粗暴一点理解,ivar 就是成员变量
objc_ivar里面又有什么内容呢?见下图
methodlists(方法列表)
同ivars一样理解,实际上这两个结构体几乎是一样的,也是一个objc_method_list 的结构体, 也是一个链表,存储多个objc_method, 而objc_method 又是一个结构体,用来存储类的某个方法信息
我们也可以来一张图来展示objc_method的风采
看到SEL估计都不陌生了,一个方法的SEL 其实是一个C的字符串,并且会在OC的runtime中注册这个selector,这个操作一般是在加载到内存的时候就完成了这一步注册操作,即在+ load 的方法中
实际上我们平时调用方法,都不是一步到位,实际上是通过selector找到该方法,然后再找到这个方法的实现(IMP),IMP本质上就是一个函数指针,指向c函数
举个栗子方便理解:
[ a sayHello] ---> 在a对象中通过其isa指针找到其类对象--->找到methodList ---> 根据"sayHello"这个selector找到对应的方法 ---> 找到其对应的IMP指针 ---> c函数
以上仅仅是方便理解而已,因为实际上,上面调用方法的环节还要加入缓存池的操作(cache)
Cache(方法缓存池)
二话不说,先上图
Cache其实就是一个存储Method的链表,主要是为了优化方法调用的性能。当调用一个方法的时候,会先去缓存池找,如果没找到,再去methodLists查找
------------------------------------------又是华丽的分割线-----------------------------
消息发送
当调用一个方法时[a sayHello],其实是被编译器转化为
objc_msgSend ( id self, SEL op, ... )
1.根据a实例对象的isa找到其对应的类对象
2.优先在类对象中的cache查找sayHello方法,如果找不到,再到methodLists查找
3.如果找不到,通过super_class 指针向父类查找,如果找不到,找父类的父类,一直到NSObject
4.一旦找到了,则执行IMP的实现
容错机制
要是找不到怎么办?一开始我也以为会抛出异常,崩溃,实际上在崩溃之前还有三次容错机制进行处理,按照以下顺序:
Method Resolution
Fast Forwarding
Normal Forwarding
上图最直接:
Method Resolution
首先Objective-C在运行时调用+ resolveInstanceMethod:或+ resolveClassMethod:方法,让你添加方法的实现。如果你添加方法并返回YES,那系统在运行时就会重新启动一次消息发送的过程。
举一个简单例子,定义一个类Message,它主要定义一个方法sendMessage,下面就是它的设计与实现:
@interface Message : NSObject
- (void)sendMessage:(NSString *)word;
@end
@implementation Message
- (void)sendMessage:(NSString *)word
{
NSLog(@"normal way : send message = %@", word);
}
@end
如果我在viewDidLoad方法中创建Message对象并调用sendMessage方法:
- (void)viewDidLoad {
[super viewDidLoad];
Message *message = [Messagenew];
[message sendMessage:@"Sam Lau"];
}
控制台会打印以下信息:
normal way : send message = Sam Lau
但现在我将原来sendMessage方法实现给注释掉,覆盖resolveInstanceMethod方法:
#pragma mark - Method Resolution
/// override resolveInstanceMethod or resolveClassMethod for changing sendMessage method implementation
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if(sel == @selector(sendMessage:)) {
class_addMethod([selfclass], sel, imp_implementationWithBlock(^(id self, NSString *word) {
NSLog(@"method resolution way : send message = %@", word);
}),"v@*");
}
returnYES;
}
控制台就会打印以下信息:
method resolution way : send message = Sam Lau
注意到上面代码有这样一个字符串"v@*,它表示方法的参数和返回值,详情请参考Type Encodings。
如果resolveInstanceMethod方法返回NO,运行时就跳转到下一步:消息转发(Message Forwarding)。
Fast Forwarding
如果目标对象实现- forwardingTargetForSelector:方法,系统就会在运行时调用这个方法,只要这个方法返回的不是nil或self,也会重启消息发送的过程,把这消息转发给其他对象来处理。否则,就会继续Normal Fowarding。
继续上面Message类的例子,将sendMessage和resolveInstanceMethod方法注释掉,然后添加forwardingTargetForSelector方法的实现:
#pragma mark - Fast Forwarding
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(sendMessage:)) {
return[MessageForwardingnew];
}
return nil;
}
此时还缺一个转发消息的类MessageForwarding,这个类的设计与实现如下:
@interface MessageForwarding : NSObject
- (void)sendMessage:(NSString *)word;
@end
@implementation MessageForwarding
- (void)sendMessage:(NSString *)word
{
NSLog(@"fast forwarding way : send message = %@", word);
}
@end
此时,控制台会打印以下信息:
fast forwarding way : send message = Sam Lau
这里叫Fast,是因为这一步不会创建NSInvocation对象,但Normal Forwarding会创建它,所以相对于更快点。
Normal Forwarding
如果没有使用Fast Forwarding来消息转发,最后只有使用Normal Forwarding来进行消息转发。它首先调用methodSignatureForSelector:方法来获取函数的参数和返回值,如果返回为nil,程序会Crash掉,并抛出unrecognized selector sent to instance异常信息。如果返回一个函数签名,系统就会创建一个NSInvocation对象并调用-forwardInvocation:方法。
继续前面的例子,将forwardingTargetForSelector方法注释掉,添加methodSignatureForSelector和forwardInvocation方法的实现:
#pragma mark - Normal Forwarding
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
if(!methodSignature) {
methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
}
return methodSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
MessageForwarding *messageForwarding = [MessageForwardingnew];
if([messageForwarding respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:messageForwarding];
}
}
-----------------------------------华丽的分割线-------------------------------------------
结束语: runtime是个好东西,虽然本人平时日常开发很少用到,但是了解其原理以及底层结构,会让你对整个oc的运作有一个更好的理解
最后,本文章也是参考刘耀柱大神的文章写出来的,更多关于runtime的干货,在下面链接:
http://www.csdn.net/article/2015-07-06/2825133-objective-c-runtime/6