Runtime

Runtime

概念

Runtime是一套底层纯C语言API,OC代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式,这也是OC作为动态语言使用的基础。Runtime的最大特征就是实现了OC语言的动态特性。

消息机制原理

在Object-C的语言中,对象方法调用都是类似[receiver selector] 的形式,其本质:就是让对象在运行时发送消息的过程。

而方法调用[receiver selector] 分为两个过程:

  • 编译阶段

[receiver selector] 方法被编译器转化,分为两种情况:

  1. 不带参数的方法被编译为:objc_msgSend(receiver,selector)
  2. 带参数的方法被编译为:objc_msgSend(recevier,selector,org1,org2,…)
  • 运行时阶段

消息接收者recever寻找对应的selector,也分为两种情况:

  1. 接收者能找到对应的selector,直接执行接收receiver对象的selector方法。
  2. 接收者找不到对应的selector,消息被转发或者临时向接收者添加这个selector对应的实现内容,否则崩溃

总而言之:

OC调用方法[receiver selector],编译阶段确定了要向哪个接收者发送message消息,但是接收者如何响应决定于运行时的判断。

重要概念

objc_msgSend

所有 Objective-C 方法调用在编译时都会转化为对 C 函数 objc_msgSend 的调用。objc_msgSend(receiver,selector); 是 [receiver selector]; 对应的 C 函数。

Object(对象)

objc/runtime.h 中Object(对象) 被定义为指向 objc_object 结构体 的指针,objc_object结构体 的数据结构如下:

//runtime对objc_object结构体的定义
struct objc_object {Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};//id是一个指向objc_object结构体的指针,即在Runtime中:
typedef struct objc_object *id;//OC中的对象虽然没有明显的使用指针,但是在OC代码被编译转化为C之后,每个OC对象其实都是拥有一个isa(指向对象的类)的指针的

Class(类)

objc/runtime.h 中Class(类) 被定义为指向 objc_class 结构体 的指针,objc_class结构体 的数据结构如下:

//runtime对objc_class结构体的定义
struct objc_class {Class _Nonnull isa;                                          // objc_class 结构体的实例指针#if !__OBJC2__Class _Nullable super_class;                                 // 指向父类的指针const char * _Nonnull name;                                  // 类的名字long version;                                                // 类的版本信息,默认为 0long 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};//class是一个指向objc_class结构体的指针,即在Runtime中:
typedef struct objc_class *Class; 

SEL (方法选择器)

typedef struct objc_selector *SEL;//Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL

1.不同类中相同名字的方法对应的方法选择器是相同的。
2.即使是同一个类中,方法名相同而变量类型不同也会导致它们具有相同的方法选择器。

获取SEL有三种方法:

1.OC中,使用@selector(“方法名字符串”)
2.OC中,使用NSSelectorFromString(“方法名字符串”)
3.Runtime方法,使用sel_registerName(“方法名字符串”)

Method(方法)

objc/runtime.h 中Method(方法) 被定义为指向 objc_method 结构体 的指针,在objct_class定义中看到methodLists,其中的元素就是Method,objc_method结构体 的数据结构如下:

struct objc_method {SEL _Nonnull method_name;                    // 方法名char * _Nullable method_types;               // 方法类型IMP _Nonnull method_imp;                     // 方法实现
};//Method表示某个方法的类型
typedef struct objc_method *Method;

Runtime消息转发

动态方法解析:动态添加方法

Runtime足够强大,能够在运行时动态添加一个未实现的方法,这个功能主要有两个应用场景:

1. 动态添加未实现方法,解决代码中因为方法未找到而报错的问题
2. 利用懒加载思路,若一个类有很多个方法,同时加载到内存中会耗费资源,可以使用动态解析添加方法

方法动态解析主要用到的方法如下:

//OC方法:
//类方法未找到时调起,可于此添加类方法实现
+ (BOOL)resolveClassMethod:(SEL)sel//实例方法未找到时调起,可于此添加实例方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel//Runtime方法:
/**运行时方法:向指定类中添加特定方法实现的操作@param cls 被添加方法的类@param name selector方法名@param imp 指向实现方法的函数指针@param types imp函数实现的返回值与参数类型@return 添加方法是否成功*/
BOOL class_addMethod(Class _Nullable cls,SEL _Nonnull name,IMP _Nonnull imp,const char * _Nullable types)
  • 解决方法无响应崩溃问题

执行OC方法其实就是一个发送消息的过程,若方法未实现,可以利用方法动态解析与消息转发来避免程序崩溃,这主要涉及下面一个处理未实现消息的过程:

在这个过程中,可能还会使用到的方法有:

img

例子:

#import "ViewController.h"
#import <objc/runtime.h>@interface ViewController ()
@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 执行 fun 函数[self performSelector:@selector(fun)];
}// 重写 resolveInstanceMethod: 添加对象方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel {if (sel == @selector(fun)) { // 如果是执行 fun 函数,就动态解析,指定新的 IMPclass_addMethod([self class], sel, (IMP)funMethod, "v@:");return YES;}return [super resolveInstanceMethod:sel];
}void funMethod(id obj, SEL _cmd) {NSLog(@"funMethod"); //新的 fun 函数
}
@end//日志输出:2019-09-01 23:24:34.911774+0800 XKRuntimeKit[3064:521123] funMethod

从执行任务的输出日志中,可以看到:

虽然没有实现 fun 方法,但是通过重写 resolveInstanceMethod: ,利用 class_addMethod 方法添加对象方法实现 funMethod 方法,并执行。从打印结果来看,成功调起了funMethod 方法。

消息接收者重定向

如果上一步中 +resolveInstanceMethod:或者 +resolveClassMethod: 没有添加其他函数实现,运行时就会进行下一步:消息接受者重定向。

如果当前对象实现了 -forwardingTargetForSelector:Runtime 就会调用这个方法,允许将消息的接受者转发给其他对象,其主要方法如下:

//重定向类方法的消息接收者,返回一个类
- (id)forwardingTargetForSelector:(SEL)aSelector//重定向实例方法的消息接受者,返回一个实例对象
- (id)forwardingTargetForSelector:(SEL)aSelector

例子:

#import "ViewController.h"
#import <objc/runtime.h>@interface Person : NSObject
- (void)fun;
@end@implementation Person- (void)fun {NSLog(@"fun");
}
@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 执行 fun 方法[self performSelector:@selector(fun)];
}+ (BOOL)resolveInstanceMethod:(SEL)sel {return YES; // 为了进行下一步 消息接受者重定向
}// 消息接受者重定向
- (id)forwardingTargetForSelector:(SEL)aSelector {if (aSelector == @selector(fun)) {return [[Person alloc] init];// 返回 Person 对象,让 Person 对象接收这个消息}return [super forwardingTargetForSelector:aSelector];
}//日志输出:2019-09-01 23:24:34.911774+0800 XKRuntimeKit[3064:521123] fun

从执行任务的输出日志中,可以看到:

虽然当前 ViewController 没有实现 fun 方法,+resolveInstanceMethod: 也没有添加其他函数实现。
但是我们通过 forwardingTargetForSelector 把当前 ViewController 的方法转发给了 Person 对象去执行了。

通过forwardingTargetForSelector 可以修改消息的接收者,该方法返回参数是一个对象,如果这个对象是不是 nil,也不是 self,系统会将运行的消息转发给这个对象执行。否则,继续进行下一步:消息重定向流程

消息重定向

如果经过消息动态解析、消息接受者重定向,Runtime 系统还是找不到相应的方法实现而无法响应消息,Runtime 系统会利用 -methodSignatureForSelector: 方法获取函数的参数和返回值类型。

其过程:

  1. 如果 -methodSignatureForSelector: 返回了一个 NSMethodSignature 对象(函数签名),Runtime 系统就会创建一个 NSInvocation 对象,
    并通过 -forwardInvocation: 消息通知当前对象,给予此次消息发送最后一次寻找 IMP(指向实现方法的函数指针) 的机会。
  2. 如果 -methodSignatureForSelector: 返回 nil。则 Runtime 系统会发出 -doesNotRecognizeSelector: 消息,程序也就崩溃了。

所以可以在-forwardInvocation:方法中对消息进行转发。

其主要方法:

// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation;// 获取函数的参数和返回值类型,返回签名
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector;

例子:

#import "ViewController.h"
#import <objc/runtime.h>@interface Person : NSObject
- (void)fun;
@end@implementation Person
- (void)fun {NSLog(@"fun");
}
@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 执行 fun 函数[self performSelector:@selector(fun)];
}+ (BOOL)resolveInstanceMethod:(SEL)sel {return YES; // 为了进行下一步 消息接受者重定向
}- (id)forwardingTargetForSelector:(SEL)aSelector {return nil; // 为了进行下一步 消息重定向
}// 获取函数的参数和返回值类型,返回签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {if ([NSStringFromSelector(aSelector) isEqualToString:@"fun"]) {return [NSMethodSignature signatureWithObjCTypes:"v@:"];}return [super methodSignatureForSelector:aSelector];
}// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {SEL sel = anInvocation.selector;   // 从 anInvocation 中获取消息Person *p = [[Person alloc] init];if([p respondsToSelector:sel]) {   // 判断 Person 对象方法是否可以响应 sel[anInvocation invokeWithTarget:p];  // 若可以响应,则将消息转发给其他对象处理} else {[self doesNotRecognizeSelector:sel];  // 若仍然无法响应,则报错:找不到响应方法}
}
@end//日志输出:
2019-09-01 23:24:34.911774+0800 XKRuntimeKit[30032:8724248] fun

从执行任务的输出日志中,可以看到:

在 -forwardInvocation: 方法里面让 Person 对象去执行了 fun 函数

问:既然 -forwardingTargetForSelector:-forwardInvocation: 都可以将消息转发给其他对象处理,那么两者的区别在哪?

答:区别就在于 -forwardingTargetForSelector: 只能将消息转发给一个对象。而 -forwardInvocation: 可以将消息转发给多个对象。

Runtime的应用

动态方法交换

实现动态方法交换(Method Swizzling )是Runtime中最具盛名的应用场景,其原理是:

通过Runtime获取到方法实现的地址,进而动态交换两个方法的功能。

类目添加新的属性

在日常开发过程中,常常会使用类目Category为一些已有的类扩展功能。虽然继承也能够为已有类增加新的方法,而且相比类目更是具有增加属性的优势,但是继承毕竟是一个重量级的操作,添加不必要的继承关系无疑增加了代码的复杂度。

获取类详细属性

  • 获取属性列表
  • 获取所有成员变量
  • 获取所有方法
  • 获取当前遵循的所有协议

解决同一方法高频率调用的效率问题

Runtime源码中的IMP作为函数指针,指向方法的实现。通过它,可以绕开发送消息的过程来提高函数调用的效率。当需要持续大量重复调用某个方法的时候,会十分有用。

动态操作属性

  • 修改私有属性
  • 改进iOS归档和解档
  • 实现字典与模型的转换

利用Runtime实现的思路大体如下:

借助Runtime可以动态获取成员列表的特性,遍历模型中所有属性,然后以获取到的属性名为key,在JSON字典中寻找对应的值value;再将每一个对应Value赋值给模型,就完成了字典转模型的目的。

Swift中的Runtime

Swift是静态语言,本身没有动态特性。

结论:

  • 对于纯Swift类来说,没有动态特性。方法和属性不加任何修饰符的情况下,这个时候已经不具备我们所谓的Runtime特性了。
  • 对于纯Swift类,方法和属性添加@objc标识的情况下,当前我们可以通过Runtime API拿到,但是在我们的OC中是没办法进行调度的。
  • 对于继承自NSObject类来说,如果我们想要动态的获取当前的属性和方法,必须在其声明前添加@objc关键字,方法交换需要添加 dynamic 标识,否则也是无法通过Runtime API获取的。

反射

反射是Swift中动态获取的一种方法,可以动态获取类型、成员信息,在运行时可以调用方法、属性等行为的特性。上面的结论说了对于一个纯Swift类来说,并不支持像OC那样操作,但是Swift标准库依然提供了反射机制让我们访问成员信息。

用法如下:

import UIKit//下方OC的部分可以不加没问题
class LGTeacher: NSObject{@objc var age: Int = 18@objc dynamic func teach(){print("teach")}
}let t = LGTeacher()let mirror = Mirror(reflecting: t.self)
for pro in mirror.children{print("\(pro.label):\(pro.value)")
}

运行结果:

Optional("age"):18

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/267380.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

虾皮选品网:知虾选品工具

使用虾皮&#xff08;Shopee&#xff09;平台进行选品时&#xff0c;许多卖家都会寻求数据软件和工具的帮助&#xff0c;以分析市场趋势、竞争程度和产品潜力。在这篇文章中&#xff0c;我们将介绍一些推荐使用的虾皮选品数据软件&#xff0c;并探讨它们如何帮助您更好地了解市…

低功耗模式的通用 MCU ACM32F0X0 系列,具有高整合度、高抗干扰、 高可靠性的特点

ACM32F0X0 系列是一款支持多种低功耗模式的通用 MCU。集成 12 位 1.6 Msps 高精度 ADC 以及比 较器、运放、触控按键控制器、段式 LCD 控制器&#xff0c;内置高性能定时器、多路 UART、LPUART、SPI、I2C 等丰富的通讯外设&#xff0c;内建 AES、TRNG 等信息安全模块&#xff0…

排序算法:【冒泡排序】、逻辑运算符not用法、解释if not tag:

注意&#xff1a; 1、排序&#xff1a;将一组无序序列&#xff0c;调整为有序的序列。所谓有序&#xff0c;就是说&#xff0c;要么升序要么降序。 2、列表排序&#xff1a;将无序列表变成有序列表。 3、列表这个类里&#xff0c;内置排序方法&#xff1a;sort( )&#xff0…

自编码器 AutoEncoder

自编码器&#xff08;AutoEncoder&#xff09;&#xff0c;也称自编码模型&#xff0c;是一种基于无监督学习的数据维度压缩和特征表示方法&#xff0c;目的是对一组数据学习出一种表示。1986年 Rumelhart 提出自编码模型用于高维复杂数据的降维。由于自动编码器通常应用于无监…

〖大前端 - 基础入门三大核心之JS篇(51)〗- 面向对象之认识上下文与上下文规则

说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费&#xff0c;如需要项目实战或者是体系化资源&#xff0c;文末名片加V&#xff01;作者&#xff1a;哈哥撩编程&#xff0c;十余年工作经验, 从事过全栈研发、产品经理等工作&#xff0c;目前在公司…

web漏洞原理与防御策略,web漏洞怎么挖掘

目录 Web安全的重要性 ​编辑常见的Web漏洞类型及其原理&#xff1a; 1、跨站脚本攻击&#xff08;XSS&#xff09;: 2、SQL注入: 3、跨站请求伪造&#xff08;CSRF&#xff09;: 4、远程文件包含&#xff08;RFI&#xff09;和本地文件包含&#xff08;LFI&#xff09;:…

ONLYOFFICE协作空间2.0:文档协作更便捷

〇、前言 大家好我是陈橘又青&#xff0c;在之前的文章中&#xff0c;我向大家介绍了ONLYOFFICE&#xff1a;免费、开源、跨平台的办公神器&#xff0c;想必大家都已经了解到了ONLYOFFICE在企业办公、文档处理工作中的方便快捷。ONLYOFFICE 2.0版本也已于近日更新。 今天就来跟…

市场全局复盘 20231211

昨日回顾&#xff1a; SELECT TOP 10000 CODE,成交额排名,净流入排名,代码,名称,DDE大单金额,涨幅,所属行业,主力净额,DDE大单净量,CONVERT(DATETIME, 最后涨停时间, 120) AS 最后涨停时间 FROM dbo.全部&#xff21;股20231208_ALL WHERE 连板天 > 1AND DDE大单净量 > …

ArcGIS无法绘制一个或多个图层

背景&#xff1a;在导入一份数据时候&#xff0c;arcmap出现无法绘制一个或多个图层的错误&#xff0c;...点数少于要素所要求的的数量&#xff0c;查阅了半天资料发现是制作数据时候拓扑关系错误造成&#xff0c;现将处理方法详细记录如下&#xff1a; 1.原数据&#xff1a; …

NSSCTF Crypto靶场练习,21-30wp

文章目录 [AFCTF 2018]你能看出这是什么加密么[LitCTF 2023]你是我的关键词(Keyworld)[NSSCTF 2022 Spring Recruit]classic[SWPUCTF 2021 新生赛]crypto4[LitCTF 2023]家人们&#xff01;谁懂啊&#xff0c;RSA签到都不会 (初级)[SWPUCTF 2021 新生赛]crypto5[LitCTF 2023]Is …

使用Microsoft Dynamics AX 2012 - 7. 库存管理

库存和仓库管理的主要职责是按数量和价值控制物料库存。若要完成此任务&#xff0c;您只能通过过帐库存交易记录来更改Dynamics AX中的库存&#xff0c;而这些交易记录之前需要在凭证中注册。 因此&#xff0c;库存中的当前数量始终是物料出库和入库交易记录的总和。大多数交易…