Runtime是什么?
Runtime是oc语言实现动态的核心
- 将尽可能多的决策从编译时和链接时推迟到运行时
- 运行时系统充当着Object-C语言的操作系统,它使语言能够工作
Runtime用来干什么?用在哪些地方?
用来干什么 基本作用
- 在程序运行过程中,动态的创建类,动态添加、修改这个类的属性和方法;
- 遍历一个类中所有的成员变量、属性、以及所有方法
- 消息传递、转发
用在哪些地方 Runtime的典型事例
- 给系统分类添加属性、方法
- 方法交换
- 获取对象的属性、私有属性
- 字典转换模型
- KVC、KVO
- 归档(编码、解码)
- 类的自我检测
Runtime的消息发送
其中有个方法objc_msgSend就是消息发送的方法,在Object-C中所有调用的方法,都是向这个类、或者发送消息
- Objc-msgSend所做的事情
1.找到方法的实现,由于通过单独的类以不同方式创建相同的方法,因此这个方法的实现的确定取决于接收消息的类对象,也即是说多个实例类对戏那个可以创建同样的方法,每个实例对象中的该方法都是独立存在的。
2.调用该方法实现,将接收消息类指针,以及该方法的参数传递给这个类
3.最后将过程的返回值作为自己的返回值传递
Objc-msgSend的发送过程
1.消息发送给对象时,消息传递函数遵循对象的isa指针指向类结构的指针,在该结构中它查询结构体变量methodLists中的方法SEL(方法选择器)
2.如在isa指向的类结构中找不到SEL(方法选择器),Objc_msgSend会跟随指向Supercalss(父类)指针并再次尝试查找该SEL
3.如连续失败直到NSObject类,它的superclass也就是它自己本身
一旦找到SEL,该函数就会调用methodLists的方法并将接收对象的指针传给它
上述过程就是Runtime的是实现方式,在面向对象的编程属于中,方法动态的绑定到消息。
加速消息发送
有的时候在一个类会有继承关系,Objective-C中大部分对象都是继承于NSObject、自己自定义类,在这种继承体系当中有很多的方法,这些方法有可能不会用到,在向类发送消息的时候,去methodLists中查找无疑会拖慢程序的运行速度,所以Apple在开发的时候加入了cache的概念,也就是缓存
在每个类中都会有一个单独的缓存,它可以包含继承过来的方法SEL以及自己定义的SEL,在搜索methodLists之前,消息传递程序会检查接受者对象的告诉缓存cache,如果找到,就不会在去搜索庞大的methodLists列表,一旦在缓存当中存在你需要的SEL,这样以后也就比函数调用稍微慢一点
理论上cache缓存的是一些会再次调用的SEL,当写的程序预热足够时间,那么所有发送过的SEL都会在cache中找到
cache会动态增长,容纳新的消息,知道程序中所有调用的SEL运行一遍为止
原理时:好比是 通常小圈子找人总比大圈子找人要快
总结
消息机制就是向接收者发送消息,并带有参数,根据接收者对象的数据结构,找到相关发放实现,最后达到这个消息的目的
objc_msgSend是Runtime的核心,Objective-C中调用对象方法就是消息传递。
objc_msgSend并不是直接调用方法实现(IMP)而是发送消息,让类的结构体去动态查到方法实现,所以在为查找到方法实现之前我们可以动态的去修改这个方法的实现
Runtime的消息转发
引言
iOS的消息转发机制,在我们开发中有时候忘记实现某个声明的方法,从而在运行过程中调用该方法出现崩溃,
当然这类问题是可以解决的,在当前对象或者父类对象中添加对象的方法实现,再重新运行,调用该方法就能解决这个问题
又或者在我们运行的时候动态的去添加接收者中未知方法实现
解决方案
第一种方式
遇到这种情况通常第一个想法就是在该对象或继承树中的实现文件中添加该方法并实现,这种形式,就是你必须要去实现方法,需要开发者主动去写代码。
第二种方式
1.动态方法解析
+(BOOL)resolveInstanceMethod:(SEL)sel //实例方法解析
+(BOOL)resolveClassMethod:(SEL)sel// 类方法解析
当运用消息转发运行时,根据调用的方法类型调用这两个方法其中一个,返回值BOOL类型,告诉系统该消息是否被处理,YES处理 NO 未处理
- resolveInstanceMethod实例方法调用
- resolveClassMethod类方法调用
这样的作用是,当接受者接受到的消息方法并没有找到的情况下,系统会调用该函数,给予这个对象一次动态添加该消息方法实现的机会,如果该对象动态的添加了这个方法的实现,就返回YES,告诉系统这个消息我已经处理完毕。再次运行该方法
注意:
当这个对象在实现了resolveInstanceMethod,resolveClassMethod两个方法,并没有对该对象消息进行处理,那么该方法会被调用两次:
一次是没有找到该方法需要对象解析处理;第二次是告诉系统我处理完成需要再次调用该方法但实际上并没有处理完成,所以会调用第二次该方法崩溃
2.后备接收者对象
-(id)forwardingTargetForSelector:(SEL)aSelector
在消息转发第一次方法解析中没有处理方法,并告诉系统本对象无法处理,需另寻办法,那么系统会给予另外一个办法,就是让别的对象B来处理该问题,如果对象B能够处理该消息,那么该消息转发结束。
将未知SEL作为参数传入,寻找另外对象处理,如果可以处理,返回该对象
3.以其他形式实现该消息方法
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
-(void)forwardInvocation:(NSInvocation *)anInvocation
当我们在前面两个步骤都没有处理该未知SEL
时,就会到第三个步骤,上述两个方法是最后寻找IML
的机会
将未知SEL作为参数传入
methodSignatureForSelector
,在该方法中处理该消息,一旦能够处理,返回方法签名(自由的修改方法签名,apple签名),让后续forwardInvocation
来进行处理在
forwardInvocation
中我们可以做很多的操作,这个方法比forwardingTargetForSelector
更灵活也可以做到像
forwardingTargetForSelector
一样的结果,不同的是一个是让别的对象去处理,后者是直接切换调用目标,也就是该方法的Target
我们也可以修改该方法的SEL,就是重新替换一个新的SEL
....
4.直到最后未处理,抛出异常
(void)doesNotRecognizeSelector:(SEL)aSelector
END
附:Runtime常用方法
Runtime 方法交换
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod([self class], @selector(originalMethod));
Method swizzlingMethod = class_getInstanceMethod([self class], @selector(swizzlingMethod));
BOOL isAdded = class_addMethod([self class], method_getName(originalMethod), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
//添加不存在的方法,返回Yes;
// Method originalMethodTest = class_getInstanceMethod([self class], @selector(originalMethodTest));
// BOOL isAddedTest = class_addMethod([self class], method_getName(originalMethodTest), method_getImplementation(originalMethodTest), method_getTypeEncoding(originalMethod));
if (isAdded)
{
NSLog(@"方法在原类中不存在,已经添加成功,用下面的方法替换其实现");
class_replaceMethod([self class], method_getName(originalMethod), method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod));
}
else
{
NSLog(@"方法在原类中存在,用下面的方法交换其实现方法");
method_exchangeImplementations(originalMethod, swizzlingMethod);
}
NSLog(@"end");
});
}
- (void)originalMethod{
NSLog(@"原类中的方法");
}
- (void)swizzlingMethod{
NSLog(@"替换过的方法");
}
Runtime 动态添加属性方法
在Objective-C中,category分类默认只能添加方法,不能添加属性。根本原因在于声明了@property后,category并不会自动生成set和get方法。如果有需要在category中添加属性,可以利用runtime的特性实现。
动态添加属性
//新建一个NSObject的category类,并添加一个customString属性
@interface NSObject (Category)
@property(nonatomic,copy)NSString *customString;
@end
//在.m文件中实现set、get方法,此时添加属性代码便完成了,就是如此简单
#import "NSObject+Category.h"
#import <objc/message.h>
- (void)setCustomString:(NSString *)customString {
objc_setAssociatedObject(self, &customStringKey, customString, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)customString {
return objc_getAssociatedObject(self, &customStringKey);
}
//测试一下,如果打印出1111,就代表添加属性成a国
- (void)viewDidLoad {
[super viewDidLoad];
///动态添加属性
NSObject *objct = [[NSObject alloc] init];
objct.customString = @"1111";
NSLog(@"%@",objct.customString);
}
动态添加方法
///例如我们有一个people类,people类中没有任何属性和方法,
//我们为之添加一个名为sing的方法
- (void)viewDidLoad {
[super viewDidLoad];
People *people = [[People alloc] init];
//添加方法
class_addMethod([People class], @selector(sing), class_getMethodImplementation([self class], @selector(peopleSing)), "v@:");
//people调用刚添加的方法
[people performSelector:@selector(sing)];
}
- (void)peopleSing
{
NSLog(@"在唱歌");
}
END