Objective-C runtime 机制
runtime使用C语言结构体表示对象,用C语言函数表示方法,这些C语言函数和结构体被Runtime封装后,我们就可以在程序中执行创建,检查,修改类和对象和他们的方法
runtime 是 OC底层的一套C语言的API(引入 <objc/runtime.h> 或<objc/message.h>),编译器最终都会将OC代码转化为运行时代码,通过终端命令编译.m 文件:clang -rewrite-objc xxx.m可以看到编译后的xxx.cpp(C++文件)。
比如我们创建了一个对象
int main(int argc, char * argv[]) {
@autoreleasepool {
JKPerson *person = [[JKPerson alloc] init];
[person doSomething];
}
return ;
}
最终被转换为几万行代码,截取最关键的一句可以看到底层是通过runtime创建的对象
int main(int argc, char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
JKPerson *person = ((JKPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((JKPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("JKPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("doSomething"));
}
return ;
}
runtime
1、是由C、C++、汇编写成的api
2、OC运行时,装载到内存
相对应的编译时,源代码翻译
OC SWIFT JAVA 高级语言,不被机器所识别,需要编译成响应的机器语言,二进制
Objective-c程序有三种途径和运行时系统交互
1、通过Objective-c源代码,如@selector()
2、通过Foundation框架中NSObject的方法,如 iskindof
3、通过调用运行时系统给我们提供的api接口,如objc_msgSend,objc_getClass
OC对象本质是结构体
OC调用方法就是发送消息 objc_msgSend
消息的组成:((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("doSomething"));
第一个参数person消息的接收者,第二个参数sel_registerName("doSomething")方法编号
imp 函数实现的指针,sel找到imp
查看关系图
OC的Class其实是一个objc_class结构体的指针,下面是Class类的定义
typedef struct objc_class *Class;
查看objc/runtime.h中objc_class结构体的定义如下
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指针
#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; // 方法缓存列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
这个isa指针的指向就是该类对象的元类,每一个类都是它的元类的对象,元类是对类对象的描述,就像类是普通实例对象的描述一样。
每一个类里面声明的类方法,其本质就是把该类方法放到元类的方法列表上面,所以类在调用类方法时,可以想象成是元类的对象在调用一个实例方法。
A的父类是B,A的元类的父类是B的元类,B的父类是NSObject,NSObject的父类是nil,B元类的父类是NSObject的元类;特别注意的一点,NSObject的元类的父类是NSObject,NSObject的isa指针又指向NSObject的元类,所以在NSObject里面的所有方法,NSObject的元类也都拥有,1、所以用NSObject 调用任意NSObject里面的实例方法都是可以成功的,
类和元类是一个闭环,实例指向类,类指向元类,元类指向跟元类,跟元类指向自身,根元类的父类是NSObject
元类是 Class 对象的类。每个类(Class)都有自己独一无二的元类(每个类都有自己第一无二的方法列表)。这意味着所有的类对象都不同。
NSObject里面的所有实力方法,任意类都可以通过类方法调用。
所有的meta-class使用基类的meta-class作为自己的基类,对于顶层基类的meta-class也是一样,只是它指向自己而已
[obj foo] 等同于 obj_msgSend(obj,@selector(foo))
objc 在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类的方法列表以及其父类方法列表中寻找方法运行。
iOS 消息查找流程 https://www.jianshu.com/p/e6b253c8c76c
如果在层层的寻找中均位找到方法的实现,就会抛出unrecognized selector sent to XXX的异常,导致程序奔溃.
在这奔溃前,oc运行时提供了三次拯救程序的机会
1、Method resolution ,动态方法解析阶段
对应的具体方法是+(BOOL)resolveInstanceMethod:(SEL)sel 和+(BOOL)resolveClassMethod:(SEL)sel,
当方法是实例方法时调用前者,当方法为类方法时,调用后者。这个方法设计的目的是为了给类利用 class_addMethod 添加方法的机会。
// void(*)()
// 默认方法都有两个隐式参数,
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
pragma mark 消息转发第一步(实例) 如此便达到了,当此类调用未定义的实例方法时,自动调用eat函数,而避免了崩溃的情况。
// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
// 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{ if (sel == @selector(eat)) {
// 动态添加eat方法 // 第一个参数:给哪个类添加方法
// 第二个参数:添加方法的方法编号
// 第三个参数:添加方法的函数实现(函数地址)
// 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), (IMP)eat, "v@:"); } return [super resolveInstanceMethod:sel];
}
2、fowarding 方法转发,备援接收者阶段
对象的具体方法是-(id)forwardingTargetForSelector:(SEL)aSelector ,
此时,运行时询问能否把消息转给其他接收者处理,也就是此时系统给了个将这个 SEL 转给其他对象的机会。
#pragma mark 消息转发第二步, 第一步失败后执行
#pragma mark 其实只要返回对象不为self 和 nil 就会把消息转发给返回的对象
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString * str = NSStringFromSelector(aSelector);
NSString * obj = [NSString stringWithFormat:@"testClass"];
NSLog(@"方法 %@ 即将转发给 Class %@",str,[obj class]);
return obj;
}
3、 fowarding 方法转发,完整消息转发阶段
1)、首先会调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 方法,倘若返回值为nil,则runtime会发出doesNotRecognizeSelector:消息,引发异常,程序崩溃。
2)、如果返回了一个合理的函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。参数 anInvocation 中包含未处理消息的各种信息(selector\target\参数...)。
在这个方法中,可以把 anInvocation 转发给多个对象,与第二步不同,第二步只能转给一个对象
代码范例:策略模式 NSInvocation 和 路由机制原理-消息转发 HXInvocationHelper
4、如果上述3个方法都没有来处理这个消息,就会进入 NSObject 的-(void)doesNotRecognizeSelector:(SEL)aSelector方法中,抛出异常
总结一下整个消息转发的流程:
代码:
#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UILabel *displayLabel;
- (IBAction)buttonTest:(UIButton *)sender;
@end @implementation ViewController
- (IBAction)buttonTest:(UIButton *)sender {
NSLog(@"--1--");
[self performSelector:@selector(setText:) withObject:@"hello"];
}
+(BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"--2--");
return NO;
}
//+(BOOL)resolveClassMethod:(SEL)sel
//{
// NSLog(@"--2--");
// return NO;
//} -(id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"--3--");
return nil;
} -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"--4--");
NSMethodSignature *signature= [super methodSignatureForSelector:aSelector];
if (!signature) {
signature=[self.displayLabel methodSignatureForSelector:aSelector];
} return signature;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"--5--");
SEL seletor=[anInvocation selector];
if([self.displayLabel respondsToSelector:seletor]){
[anInvocation invokeWithTarget:self.displayLabel];
}
}
@end
问题:那我们只用最后一个接盘侠方法多好啊,为什么还需要前2个呢?
其实还与这3个方法的用途不同有关:
运行期添加方法,用1;
转发给另1个对象、改变方法时,用2;
需要转发给多个对象时,用3;
参考:
链接:OC最实用的runtime总结,面试、工作你看我就足够了!
Objective-C runtime 机制的更多相关文章
- Objective-C的对象模型和runtime机制
内容列表 对象模型(结构定义,类对象.元类和实例对象的关系) 消息传递和转发机制 runtime系统功能理解 对象模型 结构定义 对象(Object): OC中基本构造单元 (building blo ...
- iOS开发之深入探讨runtime机制02-runtime的简单使用
runtime机制为我们提供了一系列的方法让我们可以在程序运行时动态修改类.对象中的所有属性.方法. 下面就介绍运行时一种很常见的使用方式,字典转模型.当然,你可能会说,“我用KVO直接 setVal ...
- iOS开发之深入探讨runtime机制01-类与对象
最近有个同事问我关于“runtime机制”的问题,我想可能很多人对这个都不是太清楚,在这里,和大家分享一下我对于runtime机制的理解.要深入理解runtime,首先要从最基本的类与对象开始,本文将 ...
- Runtime机制的使用整理
一.基本概念 1.1.RunTime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制. 1.2.对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何 ...
- iOS的Runtime机制下给类别(category)添加属性、替换原有类的方法执行
一.Runtime的理解 OC是面向对象的语言这是常识,其实就是通过Runtime机制动态创建类和对象,这里只是简单的运用runtime的使用! 二.类别(category)添加属性_使用前记得导入头 ...
- Runtime机制之结构体及操作函数
一.动态语言 Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理.这种动态语言的优势在于:具有灵活性,比如:消息转发,方法交换等.它有一个运行时系统Ob ...
- Objective C Runtime 开发介绍
简介 Objective c 语言尽可能的把决定从编译推迟到链接到运行时.只要可能,它就会动态的处理事情.这就意味着它不仅仅需要一个编译器,也需要一个运行时系统来执行变异好的代码.运行时系统就好像是O ...
- runtime机制
runtime(简称运行时),是一套 纯C(C和汇编写的) 的API.而 OC 就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制. 消息机制原理:对象根据方法编号SEL去映射表查找对 ...
- iOS开发之Runtime机制深入解析
本篇主要讲述在 OC 开发中主要涉及到的运行时机制: 运行时的工作: 运行时在 OC 中的工作:OC 语言的设计模式决定了尽可能的把程序从编译和链接时推迟到运行时.只要有可能,OC 总是使用动态的方式 ...
随机推荐
- HTTP 请求头中的 X-Forwarded-For,X-Real-IP
X-Forwarded-For 在使用nginx做反向代理时,我们为了记录整个的代理过程,我们往往会在配置文件中做如下配置: location / { 省略... proxy_set_header ...
- MP实战系列(十五)之执行分析插件
SQL 执行分析拦截器[ 目前只支持 MYSQL-5.6.3 以上版本 ],作用是分析 处理 DELETE UPDATE 语句, 防止小白或者恶意 delete update 全表操作! 这里我引用M ...
- 数据同步canal服务端HA配置
canal服务端HA模式,本人并未使用过,为保证文章的完整性,从以下地址摘抄该部分内容,待以后验证及使用 https://github.com/alibaba/canal/wiki/AdminGuid ...
- Python2.7-csv
csv模块,用于读写 csv 文件,常用 reader 和 writer 对象进行操作 1.模块的类 1.1 Dialect 对象,设置 csv 文件的各种格式,包括分隔符,引用符,转义符等 1.1. ...
- 缩点tarjan
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大.你只需要求出这个权值和.允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次. 缩点含义:将一个环缩成 ...
- (二)Lua脚本语言入门(关于函数)
上一篇文章忘了插入代码了,方便粘贴复制...... 函数 对于c语言就是 void aa()//c语言是用void { print("这是一个函数") } Lua就变成了 func ...
- js 函数作为参数+接受任意数量参数
javascript中的函数是“复合数据类型”,又成为“引用类型”.引用类型的变量指向存储单元中存放的是它们的实际存放地址.函数名是对函数的一种引用.var a=max_num ;a()就可以调用fu ...
- Centos7 安装ELK日志分析
1.安装前准备 借鉴:https://www.cnblogs.com/straycats/p/8053937.html 操作系统:Centos7 虚拟机 8G内存 jdk8+ 软件包下载:采用rp ...
- hiveserver2连接报错: User: root is not allowed to impersonate anonymous (state=08S01,code=0)
使用HiveServer2运行时,启动好HiveServer后运行 private static String url = "jdbc:hive2://192.168.213.132:100 ...
- R 语言-基础
R语言 1997年成为GNU项目 开源免费 R官方网址 www.r-project.org R是数据分析领域的语言小巧灵活,通过扩展包来增强功能绘图功能代码简单 开发环境R + RStudio 1.数 ...