博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Objective-C runtime 机制
阅读量:4946 次
发布时间:2019-06-11

本文共 4898 字,大约阅读时间需要 16 分钟。

Runtime使用C语言结构体表示对象,用C语言函数表示方法,这些C语言函数和结构体被Runtime封装后,我们就可以在程序中执行创建,检查,修改类和对象和他们的方法

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对象本质是结构体

调用方法就是发送消息 objc_msgSend
消息的组成:((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run"));
第一个参数p消息的接收者,第二个参数sel_registerName("run")方法编号
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__ Classsuper_class OBJC2_UNAVAILABLE; // 父类const char *name OBJC2_UNAVAILABLE; // 类名long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0long 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指针找到该对象实际所属的类,然后在该类的方法列表以及其父类方法列表中寻找方法运行。如果在层层的寻找中均位找到方法的实现,

 就会抛出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 方法转发,完整消息转发阶段
 首先会调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 方法,倘若返回值为nil,则runtime会发出doesNotRecognizeSelector:消息,引发异常,程序崩溃。如果返回了一个合理的函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。参数 anInvocation 中包含未处理消息的各种信息(selector\target\参数...)。
 在这个方法中,可以把 anInvocation 转发给多个对象,与第二步不同,第二步只能转给一个对象
 
 如果上述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;

 

参考:

链接:

链接:

转载于:https://www.cnblogs.com/dhui69/p/6413212.html

你可能感兴趣的文章
HTML超文本标记语言(九)——表单输入类型
查看>>
基于busybox制作mini2440根文件系统及使用nfs挂载
查看>>
信道容量及信道编码原理学习
查看>>
浅谈独立特征(independent features)、潜在特征(underlying features)提取、以及它们在网络安全中的应用...
查看>>
从随机过程的熵率和马尔科夫稳态过程引出的一些思考 - 人生逃不过一场马尔科夫稳态...
查看>>
《A First Course in Abstract Algebra with Applications》-chaper1-数论-关于素数
查看>>
ORA-3136
查看>>
算法笔记_145:拓扑排序的应用(Java)
查看>>
JS获取农历日期
查看>>
PHP中的HTTP协议
查看>>
CSS给文字描边实现发光文字
查看>>
Java WebService入门实例
查看>>
css样式之补充
查看>>
结构与联合
查看>>
BUPT复试专题—众数(2014)
查看>>
20145316 《信息安全系统设计基础》第十四周学习总结
查看>>
Liferay7 BPM门户开发之18: 理解ServiceContext
查看>>
Intel Galileo development documentation
查看>>
EV: Workaround to Allow Only One Instance or Window of outlook
查看>>
数据校验,
查看>>