当前位置: 首页 > news >正文

iOS主要知识点梳理回顾-3-运行时

运行时(runtime)

        运行时是OC的重要特性,也是OC动态性的根本支撑。动态,如果利用好了,扩展性就很强。当然了,OC的动态性只能算是一个一般水平。与swift、java这种强类型校验的语言相比,OC动态性很强,和js这种纯动态的语言(随时给类增加函数、属性)相比,OC的动态性就弱很多。动态,可以帮助我们在运行时修改类的属性、函数、甚至创建一个新类

相关知识点

消息机制

        OC中的方法调用、属性读写等都是通过消息机制来处理的,当我们调用一个方法时,其实是向那个实例发送了一个消息(包含类方法,类本身是Class的实例)。所以后面的一些功能特点其实也使用了消息机制的特性,这里我们主要说说消息转发。

OC的函数调用实际上发送了一条消息,那调用过程就是消息处理过程,首先从类方法或者实例方法列表中获取你要调用的方法,如果没找到就进入转发流程。转发流程中预留了可扩展的点,大致过程是这样的

  1. 动态解析,此过程如果正确添加了方法就执行方法并结束转发,否则继续下面的步骤
  2. 备用接收者,转发给其他类/实例,如果不转发,则继续下面的步骤
  3. 完整转发,获取方法签名并转发,如果正确设置了签名并转发则结束,否则继续
  4. 调用未找到方法函数并抛出异常


动态解析

类方法动态解析(resolveClassMethod)

+ (BOOL)resolveClassMethod:(SEL)sel {NSString *selName = NSStringFromSelector(sel);NSLog(@"%s : %@", __func__, selName);if (sel == @selector(greet)) {// 动态添加 +greet 方法class_addMethod(object_getClass(self), sel, (IMP)greet1, "v@:");}return NO;
}void greet1(id self, SEL _cmd) {NSLog(@"Hello from MyClass!");
}

这里我用class_addMethod来动态添加了一个方法,方法名是greet,方法的实现指向了greet1.还应该注意到class_addMethod的第一个参数,他的类型是Class,如果写self是不生效的,需要用object_getClass(self)才可以。原因是我们要给原类增加方法,具体:

  • object_getClass(self) 返回类的 元类(meta-class),是 self 的类的类对象。
  • self和self.class 返回的是 类对象,即当前类本身。

实例方法动态解析(resolveInstanceMethod)

+ (BOOL)resolveInstanceMethod:(SEL)aSelector {if (aSelector == @selector(greet)) {class_addMethod(self, aSelector, (IMP)greet1, "v@:");}return [super resolveClassMethod:aSelector];
}

这里和类方法的创建只是第一个参数有差别,其他一样

关于返回值我没发现区别,关键还在于是不是正确的添加了方法,如果添加了则不再继续后续转发步骤,否则不论返回YES还是NO都会继续后面的步骤。

备用接收者

如果 resolveInstanceMethod(或class)没有正确的添加方法,Runtime 会调用 forwardingTargetForSelector: 方法。在这个阶段,你可以返回一个对象,如果是类方法返回类。Runtime 将会尝试将消息转发给这个对象或类,如果这个对象或类能够响应消息,消息就被解析了。后面将不再单独区分类还是实例,方法名都一样,只是+方法和-方法的区别

- (id)forwardingTargetForSelector:(SEL)aSelector {if (aSelector == @selector(greet)) {return [[OtherClass alloc] init];}return [super forwardingTargetForSelector:aSelector]; // 确保继续转发
}

完整转发

如果没有设置备用接收者,将进入最后的完整转发阶段,Runtime 会调用 methodSignatureForSelector: 方法获取方法的签名,然后再调用 forwardInvocation: 方法来处理消息。在 forwardInvocation: 中,你可以自定义消息的处理逻辑,包括选择使用哪个对象来处理消息。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}- (void)forwardInvocation:(NSInvocation *)anInvocation {// 将消息转发给 OtherClass[anInvocation invokeWithTarget:[[OtherClass alloc] init]]; 
}

关于signature的书写规则参考:

类型编码符号
voidv
id@
SEL:
inti
floatf
doubled
char **
BOOLB
struct{name=...}
pointer (void *)^v

未找到方法

如果最终消息无法解析,以上步骤都失败,Runtime 将会调用 doesNotRecognizeSelector: 方法,该方法默认会抛出异常,导致程序崩溃。

应用,消息机制是OC动态特性的一个体现,我们可以动态的处理函数调用,增加切入操作等。
 

方法交换

        方法交换多用于做一些监控或者跟踪,这个时候,本质上是把一些代码注入到了系统的关键函数中,如生命周期函数。举个例子,我想监控用户的页面浏览过程,那就可以给UIViewController的viewWillAppear做个注入(用新的viewWillAppear方法替换并包含原viewWillAppear的逻辑,所以往往是替换之后有调用了一下原方法),这样用户每打开一个页面都可以被监控到同时还保留了原viewWillAppear做的事情。

事例简单 不在赘述

类和实例的操作

我们可以通过运行时函数,

  • 创建类(objc_allocateClassPair)
  • 关联对象(objc_setAssociatedObject、objc_getAssociatedObject给类扩展属性)
  • 创建函数(class_getMethodImplementation)
  • 获取类的属性列表(class_copyPropertyList)
  • 函数列表(class_copyMethodList)

这些有啥用,属性扩展经常用,创建函数可以用于异常兜底,属性列表可以做成解析器(把json解析成具体的实例)

关类别和类的扩展,都可创建单独的文件

- 类别,就是有别名的扩展,创建类别时会生成h+m文件。既然有.m文件,那么就可以实现缺失的逻辑,就是说他可以对系统类(比如UIImage)进行扩展。如果增加函数,自己写实现;关联对象,实现增加属性的效果,自己实现其getter和setter。类别会增加类的能力,方便我们开发,但是如果扩展的是系统的类,推荐命名时做好隔离。扩展的过程,一旦出现重名,可能会带来很多不稳定结果,如果和系统函数重名,将会覆盖系统函数,如果和其他类别扩展的函数重名了,则会根据加载顺序,被后加载的文件覆盖,结果将会不稳定。

举例如下,其中括号里面的Help是类别的名称,示例中,我没有加前缀。

/// .h文件
@interface UIImage (Help)/// name
@property (nonatomic, copy) NSString *imageName;/// Get a image of target color
- (UIImage *)imageWithColor:(UIColor *)color;@end/// .m文件
@implementation UIViewController(Help)- (UIImage *)imageWithColor:(UIColor *)color {// 具体实现...return image;
}- (NSString *)imageName {return objc_getAssociatedObject(self, @selector(imageName));
}- (void)setImageName:(NSString *)imageName {objc_setAssociatedObject(self, @selector(imageName), imageName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}@end

- 类的扩展,没有别名(匿名),创建类的扩展时你会发现,他只有一个h文件,所以不给你写实现的机会。没有实现,那就只能给你有源码(自定义类)进行扩展。那可能有人说:有源码,我扩展个鸟,直接去改就行了。类的扩展可以帮助我们在不同的场景下报漏不同的属性、函数,方便管理;也可以在日常开发中书写私有属性,不对外曝露。

代码示例如下

@interface MyClass ()/// name
@property (nonatomic, copy) NSString *cName;- (void)doSomething;@end

http://www.mrgr.cn/news/90138.html

相关文章:

  • 分析模式应用――帐务模式03
  • k8s部署elasticsearch
  • 探索robots.txt:网站管理者的搜索引擎指南
  • postgresql的用户、数据库和表
  • 记录安装pdsh中 Linux 系统根目录空间不足导致的编译安装问题
  • 数字滤波器的分类
  • 力扣--链表
  • leetcode 做题思路快查
  • 大语言模型RAG,transformer
  • 【C++】命名空间
  • SqlSugar简单使用之Nuget包封装-Easy.SqlSugar.Core
  • 六、OSG学习笔记-漫游(操作器)
  • windows平台本地部署DeepSeek大模型+Open WebUI网页界面(可以离线使用)
  • git submodule使用
  • 【RabbitMQ】RabbitMQ的下载安装及使用
  • 【Vue】在Vue3中使用Echarts的示例 两种方法
  • 律所录音证据归集工具:基于PyQt6与多线程的自动化音频管理解决方案
  • 【DeepSeek】DeepSeek概述 | 本地部署deepseek
  • c语言——网络编程【多路文件IO实现 poll、epoll模型总结】内附代码
  • 大语言模型RAG,transformer和mamba
  • 使用LLaMA Factory踩坑记录
  • SQL自学,mysql从入门到精通 --- 第 7 天,表的联合
  • 机器学习 - 线性回归(最大后验估计)
  • WEB小项目——鼠标划入丝滑显示下划线
  • Wpf美化按钮,输入框,下拉框,dataGrid
  • SpringBoot3 + Jedis5 + Redis集群 如何通过scan方法分页获取所有keys