【iOS】方法交换

article/2025/6/23 9:28:12

方法交换

  • method-swizzling是什么
  • 相关API
  • 方法交换的风险
    • method-swizzling使用过程中的一次性问题
    • 在当前类中进行方法交换
    • 类方法的方法交换
  • 方法交换的应用

method-swizzling是什么

method-swizzling的含义是方法交换,他的主要作用是在运行的时候将一个方法的实现替换为另一个方法的实现,这就是我们说的iOS黑魔法

OC中,利用method-swizzling实现AOP,AOP(面向切面编程)是一种编程思想,区别于OOP。

其中,AOP是面向切面进行提取封装,提取各个模块中的公共部分,提高模块的复用率,降低业务之间的耦合性;而OOP更加倾向于对业务模块的封装,划分出更加清晰的逻辑单元。

在之前学习探索消息流程的时候,我们了解到可以通过SEL方法查找器来查找method方法,而后得到对应的IMP。而方法交换其实就是将SEL与IMP原本的对应断开,并将SEL和新的IMP生成对应关系

这里笔者附上一张看到的图片来解释其关系:

在这里插入图片描述

相关API

//通过sel获取方法Method
class_getInstanceMethod://获取实例方法
class_getClassMethod://获取类方法method_getImplementation://获取一个方法的实现
method_setImplementation://设置一个方法的实现
method_getTypeEncoding://获取方法实现的编码类型
class_addMethod://添加方法实现
class_replaceMethod://用一个方法的实现,替换另一个方法的实现,即aIMP 指向 bIMP,但是bIMP不一定指向aIMP
method_exchangeImplementations://交换两个方法的实现,即 aIMP -> bIMP, bIMP -> aIMP

方法交换的风险

下面我们来看看方法交换中会遇到的问题

method-swizzling使用过程中的一次性问题

一次性:method-swizzling写在load方法中,但是load会主动的调用多次,这会导致方法的重复交换,令方法SEL指向又恢复成原来的IMP的问题出现

故而,这里我们可以通过单例模式的原则,令方法交换仅仅执行一次,这里我们需要使用GCD来实现单例,下面举一个例子说明该问题:

+ (void)load{static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{[LGRuntimeTool lg_bestMethodSwizzlingWithClass:self oriSEL:@selector(helloword) swizzledSEL:@selector(lg_studentInstanceMethod)];});
}

在当前类中进行方法交换

当我们进行方法交换的时候,必须交换的是当前类中的方法,若是方法在父类中,直接交换会导致父类的方法被错误的修改,这样会影响其所有的子类。

  • 方法交换的隔离性:必须确保交换的目标方法是当前类已实现或动态添加的方法,避免污染父类方法列表。
  • 动态注册的重要性:通过 class_addMethod 隔离父类实现,保证交换仅作用于当前类及其子类。
  • 风险规避:在子类分类中操作父类方法需谨慎,推荐使用 method_setImplementationclass_replaceMethod 控制影响范围。

这里我们先来看看若是直接和其父类进行方法交换会引起的后果:

在这里插入图片描述

在这里插入图片描述

上面的是父类中的方法,下面在子类的分类中方法交换,我们来看看会发生什么

在这里插入图片描述

根据断点显示,我们可以发现在子类Student中方法交换之后子类调用最后结果是正确的,但是到了最后一个断点的时候,可以发现程序报错了。我们来看看这是为什么?

这里我们先看子类Student调用personInstanceMethod方法,由于其imp交换成了lg_studentInstanceMethod,而在子类的分类中有该方法,所以不会报错。但是当父类Person中的imp也被交换成了lg_studentInstanceMethod,但我们并没有在父类中实现该方法,即相关的imp无法找到,就会导致程序崩溃掉。

那么我们怎么做就可以让程序不崩溃呢,这里我们可以通过class_addMethod尝试添加要交换的方法,下面先给出示例:

在这里插入图片描述

一般交换方法: 交换自己有的方法 – 走下面 因为自己有意味添加方法失败

交换自己没有实现的方法:

  • 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)

  • 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL

当我们两个方法都没有实现的情况下,就会进入无限递归,导致最后栈溢出:

原因是 栈溢出递归死循环了,那么为什么会发生递归呢?----主要是因为 personInstanceMethod没有实现,然后在方法交换时,始终都找不到oriMethod,然后交换了寂寞,即交换失败,当我们调用personInstanceMethod(oriMethod)时,也就是oriMethod会进入LG中lg_studentInstanceMethod方法,然后这个方法中又调用了lg_studentInstanceMethod,此时的lg_studentInstanceMethod并没有指向oriMethod ,然后导致了自己调自己,即递归死循环

类方法的方法交换

其实类方法的方法交换和实例方法的方法交换差不多,这里我给出一个例子,这里和实例方法的区别其实只是类方法存在元类中

+ (void)lg_bestClassMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{if (!cls) NSLog(@"传入的交换类不能为空");Method oriMethod = class_getClassMethod([cls class], oriSEL);Method swiMethod = class_getClassMethod([cls class], swizzledSEL);if (!oriMethod) { // 避免动作没有意义// 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:class_addMethod(object_getClass(cls), oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){NSLog(@"来了一个空的 imp");}));}// 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败// 交换自己没有实现的方法://   首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)//   然后再将父类的IMP给swizzle  personInstanceMethod(imp) -> swizzledSEL//oriSEL:personInstanceMethodBOOL didAddMethod = class_addMethod(object_getClass(cls), oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));if (didAddMethod) {class_replaceMethod(object_getClass(cls), swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));}else{method_exchangeImplementations(oriMethod, swiMethod);}}

方法交换的应用

方法交换最常用的一个应用是防止数组、字典等越界崩溃。在iOS中,NSNumberNSArrayNSDictionary这些都是类簇,一个NSArray的实现可能由多个类组成,所以我们必须获取到其"真身"进行交换,直接对NSarray进行操作是无效的。

下面列举了NSArray和NSDictionary本类的类名,可以通过Runtime函数取出本类。

类名真身
NSArray__NSArrayI
NSMutableArray__NSArrayM
NSDictionary__NSDictionaryI
NSMutableDictionary__NSDictionaryM

这里我以NSArray为例来看看方法交换的应用:

#import "NSArray+crush.h"
#import "objc/objc-runtime.h"
@implementation NSArray (crush)
+ (void)load {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{NSLog(@"load");Method fromMethod = class_getInstanceMethod(objc_getClass("NSConstantArray"), @selector(objectAtIndex:));Method toMethod = class_getInstanceMethod(objc_getClass("NSConstantArray"), @selector(new_objectAtIndex:));method_exchangeImplementations(fromMethod, toMethod);});}
- (id)new_objectAtIndex:(NSUInteger)index {NSLog(@"new_objectAtIndex");if (index >= self.count) {// 越界处理NSLog(@"Index %lu out of bounds, array count is %lu.", (unsigned long)index, (unsigned long)self.count);return nil;} else {// 正常访问,注意这里调用的是替换后的方法,因为实现已经交换return [self new_objectAtIndex:index];}}@end

这里我们新建一个NSArray的分类,交换一下objectAtIndexnew_objectAtIndex方法,我们来看看结果:

在这里插入图片描述

这里的NSArray类型是NSConstantArray,虽然我们已经越界了但是程序并没有退出,我们打印了一个报错,这样就保证了这个函数的安全.


http://www.hkcw.cn/article/tQOncFiesb.shtml

相关文章

GoogLeNet网络模型

GoogLeNet网络模型 诞生背景 在2014年的ImageNet图像识别挑战赛中,一个GoogLeNet的网络架构大放异彩,与VGG不同的是,VGG用的是3*3的卷积,而GoogLeNet从1*1到7*7的卷积核都用,也就是使用不同大小的卷积核组合。 网络…

Linux:动静态库

一:什么是库 库是写好的,现有的,成熟的可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人都从零开始写,因此库的存在一样非同寻常 本质上库是一种可执行代码的二进制形式,可以被操作…

【图像处理入门】2. Python中OpenCV与Matplotlib的图像操作指南

一、环境准备 import cv2 import numpy as np import matplotlib.pyplot as plt# 配置中文字体显示(可选) plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False二、图像的基本操作 1. 图像读取、显示与保存 使用OpenCV…

设计模式——装饰器设计模式(结构型)

摘要 文中主要介绍了装饰器设计模式,它是一种结构型设计模式,可在不改变原有类代码的情况下,动态为对象添加额外功能。文中详细阐述了装饰器模式的角色、结构、实现方式、适合场景以及实战示例等内容,还探讨了其与其他设计模式的…

生活小记啊

最近生活上的事情还是蛮多的,想到哪写到哪。 工作 三月的某个周六,正在加班写技术方案,大晚上写完了听到调动通知,要去新的团队了。 还是蛮不舍的,看着产品从无到有,一路走过来,倾注了不少感…

【android bluetooth 案例分析 04】【Carplay 详解 2】【Carplay 连接之手机主动连车机】

1. 背景 在【android bluetooth 案例分析 04】【Carplay 详解 1】【CarPlay 在车机侧的蓝牙通信原理与角色划分详解】中我们从整理上介绍了车机中 carplay 相关基础概念。 本节 将详细分析 iphone手机主动 连接 车机carplay 这一过程。 先回顾一下 上一节, carpla…

【Kotlin】数字字符串数组集合

【Kotlin】简介&变量&类&接口 【Kotlin】数字&字符串&数组&集合 文章目录 Kotlin_数字&字符串&数组&集合数字字面常量显式转换数值类型转换背后发生了什么 运算字符串字符串模板字符串判等修饰符数组集合通过序列提高效率惰性求值序列的操…

FreeCAD源码分析: 串行化工具

本文分析FreeCAD中的串行化工具。 注1:限于研究水平,分析难免不当,欢迎批评指正。 注2:文章内容会不定期更新。 零、预修 0.1 QDataStream 0.2 Boost.Iostreams 0.3 Zipios 0.4 Xerces-C 一、核心组件 1.1 Base::Writer 1.2 Ba…

【R语言编程绘图-plotly】

安装与加载 在R中使用plotly库前需要安装并加载。安装可以通过CRAN进行,使用install.packages()函数。加载库使用library()函数。 install.packages("plotly") library(plotly)测试库文件安装情况 # 安装并加载必要的包 if (!requireNamespace("p…

设计模式——系统数据建模设计

摘要 本文主要介绍了UML在软件系统分析和设计中的应用,详细阐述了六大类关系(泛化、实现、依赖、关联、聚合、组合)及其在UML类图中的表示方法,并通过具体例子说明了这些关系在实际编程中的应用。同时,文章还概述了UM…

37. Sudoku Solver

题目描述 37. Sudoku Solver 回溯 class Solution {vector<vector<bool>> row_used;vector<vector<bool>> col_used;vector<vector<bool>> box_used;public:void solveSudoku(vector<vector<char>>& board) {row_used.r…

【Java开发日记】基于 Spring Cloud 的微服务架构分析

目录 1、Spring Cloud 2、Spring Cloud 的核心组件 1. Eureka&#xff08;注册中心&#xff09; 2. Zuul&#xff08;服务网关&#xff09; 3. Ribbon&#xff08;负载均衡&#xff09; 4. Hystrix&#xff08;熔断保护器&#xff09; 5. Feign&#xff08;REST转换器&a…

进程间通信IV System V 系列(linux)

目录 消息队列 原理 操作 补充概念 信号量 (原子性计数器) 原理 操作 (和共享内存相似) 总结 小知识 消息队列 原理 在内核中建立一个队列&#xff0c;进程可以相互进行通信&#xff0c;通过队列进行IPC&#xff0c;就是进程之间发送带类型的数据块。 操作 接口和共享…

【MySQL】索引(B+树详解)

MySQL(五)索引 一、索引的减I/O设计 1.读取量 2.搜索树 2.1方向 2.2有序 3.分多叉 3.1B树 弊端: 3.2B树 3.2.1非叶子-搜索字段 3.2.1.1海量分叉 3.2.1.1.1最大式 3.2.1.1.2最快式 3.2.1.2缓存内存 3.2.1.2.1字段总量小 3.2.1.2.2时间复杂度 3.2.1.3区间搜索向…

2025年全国青少年信息素养大赛复赛C++算法创意实践挑战赛真题模拟强化训练(试卷4:共计6题带解析)

2025年全国青少年信息素养大赛复赛C++算法创意实践挑战赛真题模拟强化训练(试卷4:共计6题带解析) 第1题:最佳情侣身高差(题目及解析) 题目描述 专家通过多组情侣研究数据发现,最佳的情侣身高差遵循着一个公式:(女方的身高) 1.09 =(男方的身高)。如果符合,你俩的身…

5.31 day33

知识点回顾&#xff1a; PyTorch和cuda的安装 查看显卡信息的命令行命令&#xff08;cmd中使用&#xff09; cuda的检查 简单神经网络的流程 数据预处理&#xff08;归一化、转换成张量&#xff09; 模型的定义 继承nn.Module类 定义每一个层 定义前向传播流程 定义损失函数和优…

【C++】模板

目录 1、函数模板 基本用法 函数模板的实现原理 函数模板的实例化 模板参数的匹配原则 2、类模板 类模板的定义格式 类模板的实例化 1、函数模板 基本用法 template < typename T >返回值类型 函数名(参数列表){} template 是模板的意思&#xff0c;typename是…

第六十二节:深度学习-加载 TensorFlow/PyTorch/Caffe 模型

在计算机视觉领域,OpenCV的DNN(深度神经网络)模块正逐渐成为轻量级模型部署的利器。本文将深入探讨如何利用OpenCV加载和运行三大主流框架(TensorFlow、PyTorch、Caffe)训练的模型,并提供完整的代码实现和优化技巧。 一、OpenCV DNN模块的核心优势 OpenCV的DNN模块自3.3…

Linux系统下安装配置 Nginx

Windows Nginx https://nginx.org/en/download.htmlLinux Nginx https://nginx.org/download/nginx-1.24.0.tar.gz解压 tar -zxvf tar -zxvf nginx-1.18.0.tar.gz #解压安装依赖&#xff08;如未安装&#xff09; yum groupinstall "Development Tools" -y yum…

qwen3解读

1. 模型架构 重点&#xff1a; 思维模式和非思维模式这两种不同的操作模式集成到一个模型中。这样可以让用户在这些模式间切换&#xff0c;而不是在不同模型间切换。多阶段的后培训方法&#xff1a;增强推理和非推理模式。将基础模型和人的偏好结合。 预训练阶段&#xff1a…