【iOS】方法交换

news2025/6/3 3:00:44

方法交换

  • 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:personInstanceMethod

    BOOL 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.coloradmin.cn/o/2394436.html

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

相关文章

数据结构:线性表的基本操作与链式表达

个人主页 文章专栏 成名之作——赛博算命之梅花易数的Java实现 陆续回三中,忘回漏回滴滴~感谢各位大佬的支持 一.线性表的定义和基本操作 1.1定义 线性表是具有相同数据类型的n个数据元素的有序数列,n为表长 第一个元素叫表头元素,除了他…

C++:设计模式--工厂模式

更多内容:XiaoJ的知识星球 目录 1.简单工厂模式1.1 简单工厂1.2 实现步骤1.3 实现代码1.4 优缺点 2.工厂模式2.1 工厂模式2.2 实现步骤2.3 实现代码2.4 优缺点 3.抽象工厂模式3.1 抽象工厂模式3.2 实现步骤3.3 实现代码3.4 优缺点 1.简单工厂模式 . 1.1 简单工厂 …

国产化Excel处理组件Spire.XLS教程:如何使用 C# 将 Excel(XLS 或 XLSX)文件转换为 PDF

Excel 是常见的数据处理与呈现工具,但直接共享 Excel 文件可能面临格式错乱、兼容性不足或数据泄露的风险。为了保证文档在不同平台和终端上的稳定展示,开发者常常需要将 Excel 文件转换为 PDF 格式。 本文将详细介绍如何使用 C#和.NET Excel 库——Spi…

B3623 枚举排列(递归实现排列型枚举)

B3623 枚举排列(递归实现排列型枚举) - 洛谷 题目描述 今有 n 名学生,要从中选出 k 人排成一列拍照。 请按字典序输出所有可能的排列方式。 输入格式 仅一行,两个正整数 n,k。 输出格式 若干行,每行 k 个正整数…

Fine Pruned Tiled Light Lists(精细删减的分块光照列表)

概括 在这篇文章, 我将介绍一种Tiled Light 变体,主要针对AMD Graphics Core Next(GCN)架构进行优化,我们的方法应用于游戏 古墓丽影:崛起 中,特别是我们在通过光列表生成和阴影贴图渲染之间交错进行异步计…

openresty+lua+redis把非正常访问的域名加入黑名单

一、验证lua geoIp2是否缺少依赖 1、执行命令 /usr/local/openresty/bin/opm get anjia0532/lua-resty-maxminddb 执行安装命令报错,缺少Digest/MD5依赖 2、Digest/MD5依赖 yum -y install perl-Digest-MD5 GeoIP2 lua库依赖动态库安装,lua库依赖libmaxminddb实…

使用Mathematica绘制随机多项式的根

使用ListPlot和NSolve直接绘制: (*返回系数为r和s之间整数的n次随机多项式*) eq[n_, r_, s_] : RandomInteger[{r, s}, {n}] . Array[Power[x, # - 1] &, n] (*返回给定随机多项式的根所对应的笛卡尔坐标*) sol[n_, r_, s_] : {Re[#], Im[#]} & / (x /.…

IEEE PRMVAI 2025 WS 26:计算机视觉前沿 Workshop 来袭!

宝子们,搞计算机视觉和深度学习的看过来啦!🎉 2025 年 IEEE 第三届模式识别、机器视觉和人工智能国际会议里,Workshop 26 简直是科研宝藏地! 这次 Workshop 聚焦 “Deep learning - based low - level models for comp…

360浏览器设置主题

设置默认主题: 1.右上角有个皮肤按钮 进来后,右边有个回复默认皮肤按钮。 换成彩色皮肤后,找按钮不太好找了。

最卸载器——Geek Uninstaller 使用指南

Geek Uninstaller 是一款轻量级的第三方卸载工具,专为 Windows 系统设计,提供比系统默认卸载器更彻底的应用清除能力。它体积小、绿色免安装,使用起来简单直观,但功能却不含糊。 一、为什么要用 Geek Uninstaller? Wi…

应急响应靶机-web3-知攻善防实验室

题目: 1.攻击者的两个IP地址 2.攻击者隐藏用户名称 3.三个攻击者留下的flag 密码:yj123456 解题: 1.攻击者的两个IP地址 一个可能是远程,D盾,404.php,192.168.75.129 找到远程连接相关的英文,1149代表远程连接成功…

【基于SpringBoot的图书购买系统】Redis中的数据以分页的形式展示:从配置到前后端交互的完整实现

引言 在当今互联网应用开发中,高性能和高并发已经成为系统设计的核心考量因素。Redis作为一款高性能的内存数据库,以其快速的读写速度、丰富的数据结构和灵活的扩展性,成为解决系统缓存、高并发访问等场景的首选技术之一。在图书管理系统中&…

Jupyter MCP服务器部署实战:AI模型与Python环境无缝集成教程

Jupyter MCP 服务器是基于模型上下文协议(Model Context Protocol, MCP)的 Jupyter 环境扩展组件,它能够实现大型语言模型与实时编码会话的无缝集成。该服务器通过标准化的协议接口,使 AI 模型能够安全地访问和操作 Jupyter 的核心…

PMO价值重构:从项目管理“交付机器”到“战略推手”

在数字化转型浪潮中,项目管理办公室(PMO)正经历着前所未有的角色蜕变。传统上,PMO往往被视为项目管理的“交付机器”,专注于项目的按时交付和资源分配。然而,随着企业对战略执行的重视,PMO正逐渐…

零知开源——STM32F407VET6驱动Flappy Bird游戏教程

简介 本教程使用STM32F407VET6零知增强板驱动3.5寸TFT触摸屏实现经典Flappy Bird游戏。通过触摸屏控制小鸟跳跃,躲避障碍物柱体,挑战最高分。项目涉及STM32底层驱动、图形库移植、触摸控制和游戏逻辑设计。 目录 简介 一、硬件准备 二、软件架构 三、…

力扣HOT100之动态规划:300. 最长递增子序列

这道题之前刷代码随想录的时候也刷过,现在又给忘完了。自己尝试着写了一下,发现怎么写都写不对,直接去看视频了。。我自己写的时候的定义是:考虑下标0 ~ i范围内索赔能取到的最长严格递增子序列的长度,后面发现在写递推…

在win10/11下Node.js安装配置教程

下载安装 官网链接https://nodejs.org/zh-cn 下载好以后双击打开,点击下一步 勾选,然后下一步 选择路径、下一步 下一步 配置环境 找到我们安装的文件夹,创建两个文件夹 node_global node_cache 在CMD中配置路径 npm config set p…

飞致云开源社区月度动态报告(2025年5月)

自2023年6月起,中国领先的开源软件公司飞致云以月度为单位发布《飞致云开源社区月度动态报告》,旨在向广大社区用户同步飞致云旗下系列开源软件的发展情况,以及当月主要的产品新版本发布、社区运营成果等相关信息。 飞致云开源运营数据概览&…

压缩包方式在Linux和Windows下安装mongodb

目录 安装流程安装实例1. Linux安装2. Windows安装 总结 安装流程 zip方式安装 优点:自定义性较高,可以自己控制数据、日志等文件的位置 1、下载安装包 2、解压安装包 3、创建各类文件路径 4、配置conf文件 5、使用自定义配置文件启动 安装实例 1. Li…

智慧场馆:科技赋能的艺术盛宴

智慧场馆作为城市公共服务设施数字化转型的典型代表,通过深度融合新一代信息技术,构建起全方位、智能化的运营管理体系。其功能架构不仅提升了场馆本身的运营效能,更重塑了公共服务体验模式,展现出显著的社会价值和商业潜力。 一…