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.mzph.cn/news/215498.shtml

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

相关文章

代码随想录27期|Python|Day13|栈与队列|239. 滑动窗口最大值 (一刷至少需要理解思路)|347.前 K 个高频元素 (一刷至少需要理解思路)

239. 滑动窗口最大值 单调队列 滑动窗口中的队列一直保持出口大&#xff0c;入口小的顺序。&#xff08;图&#xff1a;代码随想录&#xff09; 1、每次有新的元素进入&#xff08;也就是滑动窗口移动后&#xff09;&#xff0c;都需要先和入口的元素比较大小&#xff0c;如果…

人体关键点检测2:Pytorch实现人体关键点检测(人体姿势估计)含训练代码

人体关键点检测2&#xff1a;Pytorch实现人体关键点检测(人体姿势估计)含训练代码 目录 人体关键点检测2&#xff1a;Pytorch实现人体关键点检测(人体姿势估计)含训练代码 1. 前言 2.人体关键点检测方法 (1)Top-Down(自上而下)方法 (2)Bottom-Up(自下而上)方法&#xff1…

ubuntu install sqlmap

refer: https://github.com/sqlmapproject/sqlmap 安装sqlmap&#xff0c;可以直接使用git 克隆整个sqlmap项目&#xff1a; git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev 2.然后进入sqlmap-dev&#xff0c;使用命令&#xff1a; python s…

静态代理IP搭建步骤,静态匿名在线代理IP如何使用?

静态代理搭建步骤 1. 确定需求 在搭建静态代理之前&#xff0c;需要明确自己的需求&#xff0c;包括代理服务器的位置、访问速度、匿名性、安全性等方面的要求。 2. 选择代理服务器提供商 可以选择自己购买服务器搭建代理&#xff0c;也可以选择使用云服务提供商的代理服务…

有趣的数学 用示例来阐述什么是初值问题二

一、示例 解决以下初值问题。 解决这个初始值问题的第一步是找到一个通用的解决方案。为此&#xff0c;我们找到微分方程两边的反导数。 即 我们能够对两边进行积分&#xff0c;因为y项是单独出现的。请注意&#xff0c;有两个积分常数&#xff1a;C1和C2。求解前面的方程y给出…

电工--半导体器件

目录 半导体的导电特性 PN结及其单向导电性 二极管 稳压二极管 双极型晶体管 半导体的导电特性 本征半导体&#xff1a;完全纯净的、晶格完整的半导体 载流子&#xff1a;自由电子和空穴 温度愈高&#xff0c;载流子数目愈多&#xff0c;导电性能就愈好 型半导体&…

28. Python Web 编程:Django 基础教程

目录 安装使用创建项目启动服务器创建数据库创建应用创建模型设计路由设计视图设计模版 安装使用 Django 项目主页&#xff1a;https://www.djangoproject.com 访问官网 https://www.djangoproject.com/download/ 或者 https://github.com/django/django Windows 按住winR 输…

docker build构建报错:shim error: docker-runc not installed on system

问题&#xff1a; docker构建镜像时报错&#xff1a;shim error: docker-runc not installed on system 解决&#xff1a; ln -s /usr/libexec/docker/docker-runc-current /usr/bin/docker-runc

MySQL数据库——锁-表级锁(表锁、元数据锁、意向锁)

目录 介绍 表锁 语法 特点 元数据锁 介绍 演示 意向锁 介绍 分类 演示 介绍 表级锁&#xff0c;每次操作锁住整张表。锁定粒度大&#xff0c;发生锁冲突的概率最高&#xff0c;并发度最低。应用在MyISAM、InnoDB、BDB等存储引擎中。 对于表级锁&#xff0c;主要…

【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(一)搭建项目

前言 最近两个月一直在忙公司的项目&#xff0c;上班时间经常高强度写代码&#xff0c;下班了只想躺着&#xff0c;没心思再学习、做自己的项目了。最近这几天轻松一点了&#xff0c;终于有时间 摸鱼了 做自己的事了&#xff0c;所以到现在我总算是搭起来一个比较完整的后台管…

nrfutil工具安装

准备工作&#xff0c;下载相关安装包 链接&#xff1a;https://pan.baidu.com/s/1LWxhibf8LiP_Cq3sw0kALQ 提取码&#xff1a;2dlc 解压后&#xff0c;分别安装以下安装包 在C盘下创建目录nordic_tools&#xff0c;并将nrfutil复制到刚创建的目录下 环境变量path下添加C:\nor…

图像采集卡 Xtium™2-XGV PX8支持高速 GigE Vision 工业相机

图像采集卡&#xff08;Image Capture Card&#xff09;&#xff0c;又称图像捕捉卡&#xff0c;是一种可以获取数字化视频图像信息&#xff0c;并将其存储和播放出来的硬件设备。很多图像采集卡能在捕捉视频信息的同时获得伴音&#xff0c;使音频部分和视频部分在数字化时同步…

裸机单片机适用的软件架构

单片机通常分为三种工作模式&#xff0c;分别是 1、前后台顺序执行法 2、操作系统 3、时间片轮询法 1、前后台顺序执行法 利用单片机的中断进行前后台切换&#xff0c;然后进行任务顺序执行&#xff0c;但其实在…

Spring Boot Web

目录 一. 概述 二. Spring Boot Web 1.2.1 创建SpringBoot工程&#xff08;需要联网&#xff09; 1.2.2 定义请求处理类 1.2.3 运行测试 1.3 Web分析 三. Http协议 3.1 HTTP-概述 刚才提到HTTP协议是规定了请求和响应数据的格式&#xff0c;那具体的格式是什么呢? 3…

spring结合设计模式之策略模式

策略模式基本概念&#xff1a; 一个接口或者抽象类&#xff0c;里面两个方法&#xff08;一个方法匹配类型&#xff0c;一个可替换的逻辑实现方法&#xff09;不同策略的差异化实现(就是说&#xff0c;不同策略的实现类) 使用策略模式替换判断&#xff0c;使代码更加优雅。 …

Swagger快速上手

快速开始&#xff1a; 导入maven包 <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.7.0</version> </dependency><dependency><groupId>io.springfox<…

MongoDB在Windows系统和Linux系统中实现自动定时备份

本文主要介绍MongoDB在Windows系统和Linux系统中如何实现自动定时备份。 目录 MongoDB在Windows系统中实现自动定时备份MongoDB在Linux系统中实现自动定时备份备份步骤备份恢复 MongoDB在Windows系统中实现自动定时备份 要在Windows系统中实现自动定时备份MongoDB数据库&#…

区块链实验室(32) - 下载arm64的Prysm

Prysm是Ethereum的共识层。 1. 下载prysm.sh curl https://raw.githubusercontent.com/prysmaticlabs/prysm/master/prysm.sh --output prysm.sh && chmod x prysm.sh2. 下载x86版prysm共识客户端 ./prysm.sh beacon-chain --download-only3.下载arm64版prysm共识客…

Linux——web网站服务(一)

一、安装httpd服务器Apache网站服务 1、准备工作 为了避免发送端口冲突&#xff0c;程序冲突等现象&#xff0c;卸载使用rpm方式安装的httpd #使用命令检查是否下载了httpd [rootserver ~]# rpm -qa httpd #如果有则使用 [rootserver ~]# rpm -e httpd --nodeps Apache的配置…

抖音小店经营规则解析:避免被扣分的关键因素

抖音小店是一个受欢迎的电商平台&#xff0c;为创业者提供了良好的销售和推广机会。为了确保在抖音小店的运营中不会被扣分或出现其他问题&#xff0c;不若与众整理了几个关键的规则需要注意和遵守。 1. 产品合规性&#xff1a; 抖音小店要求所有销售的产品必须合法合规&#x…