一.概述
Runtime是一套C语言的API,基本是用 C 和汇编写的,封装了很多动态性相关的函数,在这里下到苹果维护的开源代码。主要是使用官方Api,解决我们框架性的需求。
Objective-C是一门动态语言。我们平时编写的OC代码,底层都是转换成了Runtime API进行调用。
动态:就是编译器在编译期可以只知道一个方法的名字,而不需要知道这个方法的实现,只有在运行期间调用该方法的时候,才根据方法名去找到对应方法的实现。
二.消息传递
Runtime的特性主要是消息(方法)传递
,如果消息(方法)在对象中找不到,就进行消息转发。
当程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应消息而做出不同的反应。这就是消息传递。
当执行[object doSomething]时,编译器转成消息发送objc_msgSend(object, doSomething),如果有参数则为objc_msgSend(object, doSomething,arg1,arg2,....) runtime的执行流程:
- 首先,通过
object
的isa指针
找到它的class
; - 在
class
的method list
找doSomething
; - 如果
class
中没到doSomething
,继续往它的superclass
中找 ; - 一旦找到
doSomething
这个函数,就去执行它的实现IMP
;
object的结构体:
代码语言:javascript复制//对象
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
class 的结构体:
代码语言:javascript复制//类
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
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;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
isa:是一个指向objc_class结构体的指针,而isa 是它唯一的私有成员变量,即所有对象都有isa指针(isa位置在成员变量第一个位置)
代码语言:javascript复制#if !__OBJC2__
Class _Nullable super_class /*父类*/
const char * _Nonnull name /*类名*/
long version /*版本信息*/
long info /*类信息*/
long instance_size /*实例大小*/
struct objc_ivar_list * _Nullable ivars /*实例参数链表*/
struct objc_method_list * _Nullable * _Nullable methodLists /*方法链表*/
struct objc_cache * _Nonnull cache /*方法缓存*/
struct objc_protocol_list * _Nullable protocols /*协议链表*/
#endif
} OBJC2_UNAVAILABLE;
方法列表:
代码语言:javascript复制//方法列表
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;
}
方法:
代码语言:javascript复制struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
IMP:
代码语言:javascript复制/// A pointer to the function of a method implementation. 指向一个方法实现的指针
typedef id (*IMP)(id, SEL, ...);
#endif
在iOS的Runtime中,Method通过selector和IMP两个属性,实现了快速查询方法及实现,相对提高了性能,又保持了灵活性。
三.消息转发
如果在方法列表中找不到该方法,就进行消息转发。消息转发的三个步骤: 动态方法解析,备用接收者,完整消息转发。
消息转发
1.动态方法解析 Objective-C运行时会调用 resolveInstanceMethod:(动态解析实例方法)或者 resolveClassMethod:(动态解析类方法),可以提供一个函数实现,如果添加了函数并返回YES,那运行时系统就会重新启动一次消息发送的过程。
代码语言:javascript复制- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(gotoOpen)];
}
(BOOL)resolveInstanceMethod:(SEL)sel{
if(sel==@selector(gotoOpen)){
class_addMethod([ self class], sel,(IMP)openMethod,"v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void openMethod(id obj,SEL _cmd){
NSLog(@"打开");
}
总结:虽然没有实现方法gotoOpen,但是我们通过class_addMethod添加了方法openMethod,并执行openMethod这个函数的IMP。
代码语言:javascript复制BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
Class cls: 将要给添加方法的类,传的类型 [类名 class]
SEL name: 将要添加的方法名,传的类型 @selector(方法名)
IMP imp:实现这个方法的函数 ,传的类型 1,C语言写法:(IMP)方法名 2,OC的写法:class_getMethodImplementation(self,@selector(方法名:))
//OC写法
class_addMethod([ self class], sel,class_getMethodImplementation(self, @selector(openMethod)),"v@:");
-(void)openMethod{
NSLog(@"打开");
}
const char *types:表示我们要添加的方法的返回值和参数
"v@:":v:是添加方法无返回值 @表示是id(也就是要添加的类) :表示添加的方法类型 @表示:参数类型
2.备用接收者 如果动态解析返回为NO,则执行forwardingTargetForSelector,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。
代码语言:javascript复制- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(openMethod)];
}
(BOOL)resolveInstanceMethod:(SEL)sel{
return NO;
}
-(id)forwardingTargetForSelector:(SEL)sel{
if(sel==@selector(openMethod)){
return [Person new];
}
return [super forwardingTargetForSelector:sel];
}
Person.m
-(void)openMethod{
NSLog(@"调用方法openMethod");
}
让备用的对象(Person)去响应了本身无法响应的一个SEL(openMethod)
3.完整消息转发 如果上一步返回nil,执行methodSignatureForSelector的方法手动将响应方法切换给备用响应对象。
代码语言:javascript复制- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(openMethod)];
}
(BOOL)resolveInstanceMethod:(SEL)sel{
return NO;
}
-(id)forwardingTargetForSelector:(SEL)sel{
return nil;
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
if(sel==@selector(openMethod)){
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:sel];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
//获得消息
SEL sel = [anInvocation selector];
//转发
Person *person = [[Person alloc]init];
if([person respondsToSelector:sel]){
[anInvocation invokeWithTarget:person];
}else
{
[super forwardInvocation:anInvocation];
}
}
Person.m
-(void)openMethod{
NSLog(@"调用方法openMethod");
}
如果拿到签名后的派发对象Person没有实现方法openMethod,则会执行doesNotRecognizeSelector。
代码语言:javascript复制-(void)doesNotRecognizeSelector:(SEL)aSelector{
NSLog(@"此方法不存在");
}
我们经常遇到的Crash问题:unrecognized selector sent to instance 0x100a1c2e0'
调用该方法时,经过消息传递,消息转发都没有找到该方法的实现,程序就会崩溃。