【iOS】源码阅读(六)——方法交换

文章目录

  • 方法交换
    • 什么是Method-Swizzling
    • 方法交换核心API
        • **1. 获取方法对象**
        • **2. 添加/替换方法实现**
        • **3. 交换方法实现**
        • **4. 获取方法信息**
        • **5. 修改方法实现**
      • **使用示例:完整的 Method-Swizzling 流程**
      • **注意事项**
    • 使用方法交换注意事项
      • 线程安全
      • 方法交换的影响范围

方法交换

什么是Method-Swizzling

  Method-Swizzling 翻译过来就是方法交换,它是 Objective-C 运行时(Runtime)提供的一种动态修改方法实现的机制。它的核心是通过交换两个方法的 IMP(方法实现)来实现功能的动态注入或修改,常用于 AOP(面向切面编程)、调试、日志统计等场景。

关于runtime

runtime是oc的核心机制,oc语言编程时,许多行为(如方法调用、类结构)在程序运行时才确定。

具体表现在其编程时的动态类型(对象的类型在运行时确定)、动态绑定(方法调用在运行时解析,而非编译时)、动态加载(按需加载类和资源)、消息传递机制(方法调用本质是向对象发送消息(objc_msgSend))。

关于IMP

IMP是 Objective-C 方法的底层实现,本质上是一个指向 C 函数的指针,格式为 id (*)(id, SEL, ...)

每个方法(Method)对应一个 IMP,它决定了方法被调用时的具体行为。

  在之前的学习中,我们可以知道,OC的方法调用通过 objc_msgSend 函数发送消息,运行时根据 SEL(方法名)查找对应的 IMP 并执行。

关于AOP与OOP

AOP主要解决的是横切关注点的问题,也就是那些在多个模块中重复出现的功能,比如日志、安全、事务管理等。这些功能如果分散在各个类中,会导致代码冗余和耦合度高。AOP通过切面(Aspect)将这些横切关注点模块化,从而在编译期或运行时将它们织入到目标代码中。

OOP的核心是类、对象、继承、封装和多态。它通过将数据和行为封装在对象中,利用继承和多态来实现代码的复用和扩展。

这样说可能会比较抽象,我们来举个具体的例子。

假设我们现在需要实现一个电商系统的订单模块,那么其中:

OOP的职责就类似于定义 Order 类,封装订单属性(订单号、金额、状态);实现 OrderService 类,处理订单创建、支付、取消等业务逻辑等。

AOP 的职责就类似于通过切面 LoggingAspect,自动记录订单操作的日志。通过切面 TransactionAspect,为支付方法添加事务管理。通过切面 PermissionAspect,校验用户是否有权限取消订单等。

了解完以上概念之后,我们再回来继续认识Method-Swizzling。

说白了,Method-Swizzling就是在程序运行时将一个方法的实现替换成另一个方法的实现,在OC中,每个类都维护着一个方法列表,即methodList,methodList中有不同的方法,即method,每个方法包含了方法的sel和IMP,方法交换就是将sel和IMP原本的对应断开,并将sel和新的IMP生成对应关系。如下图:

请添加图片描述

方法交换核心API

OC中的Method-Swizzling 依赖于 Runtime 提供的 API,主要用于操作类和方法的实现(IMP)。

因此,我们在使用方法交换相关API之前,要先导入Runtime相关的头文件:

#import "objc/runtime.h"
1. 获取方法对象

通过选择器(SEL)和类获取 Method 结构体,包含方法的实现(IMP)和类型编码(Type Encoding)。

class_getInstanceMethod

Method originalMethod = class_getInstanceMethod([UIViewController class], @selector(viewWillAppear:));

这是一个用于获取类中实例方法的函数,其中第一个参数是获取 UIViewController 类的类对象(Class Object);第二个参数是定义一个方法选择器(SEL),指向 viewWillAppear: 方法。这个函数最终会返回一个Method类型的结构体指针。

这里我们可以找到Method的源码:

typedef struct objc_method *Method;/// Defines a method
struct objc_method_description {SEL _Nullable name; /**< The name of the method *///方法的唯一标识符(通过 @selector(methodName) 生成)。char * _Nullable types; /**< The types of the method arguments *///方法的类型编码字符串(如 "v@:@" 表示返回 void,参数为 id 和 SEL)。
};

class_getClassMethod

Method classMethod = class_getClassMethod([NSArray class], @selector(arrayWithObject:));

这是一个用于获取类方法(Class Method)的函数,参数与返回类型同上。

2. 添加/替换方法实现

用于动态修改类的方法列表。

class_addMethod

BOOL success = class_addMethod([self class], @selector(newMethod), (IMP)newMethodIMP, "v@:");

这个函数用于向类中添加新的方法实现(该方法原不存在)。

  • Class cls:目标类。

  • SEL name:方法选择器。

  • IMP imp:方法实现(C 函数指针)。

  • const char *types:方法类型编码(如 "v@:" 表示返回 void,参数为 idSEL)。

  • 返回值BOOL,表示是否成功添加。

class_replaceMethod

class_replaceMethod([UIViewController class], @selector(viewWillAppear:), (IMP)swizzled_viewWillAppear, "v@:");

若方法存在则替换类的方法实现,若不存在则进行添加。

  • 参数:同 class_addMethod
  • 返回值BOOL,表示是否成功替换或添加。
3. 交换方法实现

直接交换两个方法的 IMP,是 Method-Swizzling 的核心操作。

method_exchangeImplementations

Method originalMethod = class_getInstanceMethod([UIViewController class], @selector(viewWillAppear:));
Method swizzledMethod = class_getInstanceMethod([self class], @selector(swizzled_viewWillAppear:));
method_exchangeImplementations(originalMethod, swizzledMethod);

交换两个 Method 对象的 IMP。

  • 参数Method m1, Method m2
  • 返回值void
4. 获取方法信息

辅助函数,用于提取方法的元数据。

method_getName

SEL selector = method_getName(originalMethod);

获取方法的 SEL。

  • 参数Method m
  • 返回值SEL

method_getTypeEncoding

const char *typeEncoding = method_getTypeEncoding(originalMethod);

获取方法的类型编码(如参数和返回值类型)。

  • 参数Method m
  • 返回值const char *

method_getImplementation

IMP originalIMP = method_getImplementation(originalMethod);

获取方法的 IMP。

  • 参数Method m
  • 返回值IMP
5. 修改方法实现

直接设置方法的 IMP。

method_setImplementation

  • 作用:直接修改方法的 IMP。
  • 参数Method m, IMP imp
  • 返回值IMP(旧实现)。
IMP oldIMP = method_setImplementation(originalMethod, newIMP);

使用示例:完整的 Method-Swizzling 流程

有两种编写方法:一种是写在需要实现方法交换的Category分类文件中;第二种就是写在专门用于 Swizzling 的类中,创建一个 MethodSwizzlingHelper.m 文件,集中管理所有 Swizzling 逻辑。

#import <objc/runtime.h>@implementation UIViewController (Swizzle)+ (void)load {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{Class class = [self class];SEL originalSelector = @selector(viewWillAppear:);SEL swizzledSelector = @selector(swizzled_viewWillAppear:);Method originalMethod = class_getInstanceMethod(class, originalSelector);Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);// 尝试添加方法(避免父类已实现但当前类未实现)BOOL didAddMethod = class_addMethod(class,originalSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));if (didAddMethod) {// 添加成功,替换原方法class_replaceMethod(class,swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));} else {// 直接交换实现method_exchangeImplementations(originalMethod, swizzledMethod);}});
}- (void)swizzled_viewWillAppear:(BOOL)animated {// 在原始方法前插入逻辑NSLog(@"View will appear: %@", NSStringFromClass([self class]));// 调用原始方法(实际执行交换后的 IMP)[self swizzled_viewWillAppear:animated];
}@end

注意事项

  1. 线程安全:在 +load 中使用 dispatch_once 确保 Swizzling 仅执行一次。
  2. 避免循环调用:在交换后的方法中调用原方法时,必须通过交换后的选择器(如 self.swizzled_viewWillAppear:)。
  3. 方法签名匹配:确保交换的方法参数和返回值类型一致,否则可能导致崩溃。
  4. 影响范围:Swizzling 会影响类及其子类的所有实例,需谨慎操作。

使用方法交换注意事项

线程安全

  mehod-swizzling方法交换一般写在load方法中,而load方法会主动调用多次,这样会导致方法的重复交换,使方法sel的指向又恢复成原来的imp。所以我们需要通过单例模式,使方法交换只执行一次,我们可以通过dispatch_once来实现单例。

方法交换的影响范围

  mehod-swizzling方法交换会影响类及其子类的所有实例。

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

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

相关文章

mysql运维问题解决:MySQL主从延迟(锁阻塞与读写分离)

小亦平台会持续给大家科普一些运维过程中常见的问题解决案例&#xff0c;运维朋友们可以在常见问题及解决方案专栏查看更多案例 问题概述 告警事件&#xff1a; 2023-07-28 03:31:39.571 首次触发主从延迟告警&#xff08;延迟1515秒&#xff09;2023-07-28 07:41:37 告警解除…

SSH 密钥

什么是 SSH 密钥 SSH 密钥就像是你电脑的“身份证”和“钥匙”&#xff0c; 用来安全登录另一台电脑&#xff08;服务器&#xff09;&#xff0c;而不需要每次输入密码。SSH 密钥是一种安全登录远程服务器的方式&#xff0c;由一对加密的“钥匙”组成&#xff1a;一个公钥 一个…

st-Gcn训练跳绳识别模型一:数据标注工具和标注流程

目录 工具展示和使用说明 工具标注后文件展示说明 json转换成单个npy文件 数据获取补充 工具展示和使用说明 文件名labelV.py集于PySide6实现&#xff1a; 通过选择视频来选择你要标注的视频&#xff0c;然后选择保存路径&#xff1a; 然后视频两个类别。当你看见视频中的人…

springboot跨域问题 和 401

springboot跨域问题 和 401 1.跨域import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotatio…

构建直播平台大体的流程

✅ 直播流程完整链路&#xff08;基于 SRS OBS 前后端&#xff09;&#x1f9cd;‍♂️ 用户操作流程&#xff1a;✅ 用户登录系统&#xff08;前端&#xff09;系统中校验用户身份&#xff08;JWT 等&#xff09;后端可能校验权限&#xff0c;比如“是否有开播资格”✅ 用户…

KOSMOS-2: 将多模态大型语言模型与世界对接

温馨提示&#xff1a; 本篇文章已同步至"AI专题精讲" KOSMOS-2: 将多模态大型语言模型与世界对接 摘要 我们介绍了 KOSMOS-2&#xff0c;一种多模态大型语言模型&#xff08;MLLM&#xff09;&#xff0c;赋予了模型感知物体描述&#xff08;例如&#xff0c;边界框…

协作机器人操作与编程-PE系统示教编程和脚本讲解(直播回放)

协作机器人操作与编程-PE系统示教编程和脚本讲解本次讲解主要围绕协作机器人PE系统的操作与编程展开&#xff0c;内容涵盖软件安装、虚拟机配置、手动操作、在线编程、变量设置、网络通信及标定方法等方面。以下是主要内容要点提炼&#xff1a; 软件安装与虚拟机配置 需从官网下…

【前后端】Node.js 模块大全

用到的全部总结在这里&#xff0c;不定期更新 链接 node一本通 包括&#xff1a; express path fs/ process/ os/ http/ mysql/mongoose/ express-jwt/jsonwebtoken/ dotenv/ multer/ swagger/ cors/ nodemon (docker篇有)常用模块 内置 fs 文件系统操作&#xff08;读写、重命…

双8无碳小车“cad【17张】三维图+设计说名书

基于MATLAB的双八无碳小车轨迹仿真及其结构设计 摘 要 本文设计的基于MATLAB的无碳小车来自于全国大学生工程训练能力竞赛&#xff0c;依据绿色环保&#xff0c;设计一种通过重力势能转换成动能来驱动小车行走的装置。通过分析任务要求&#xff0c;本文完成了小车的三维结构设计…

视觉大模型离线部署全流程优化:从微调技术到工程实践

视觉大模型离线部署全流程优化&#xff1a;从微调技术到工程实践 一、视觉大模型离线部署概述 1.1 视觉大模型的应用场景与挑战 视觉大模型在物体检测、图像生成、图像描述等领域展现出强大能力&#xff0c;已成为人工智能领域的研究热点和产业应用焦点(5)。随着技术的发…

Vue中组件的生命周期

组件的生命周期生命周期、生命周期函数、生命周期钩子vue2的生命周期创建&#xff08;创建前的生命周期函数 beforeCreate &#xff0c;创建完毕created&#xff09;挂载&#xff08;挂载前beforeMount&#xff0c;挂载完毕mounted&#xff09;//把组件放在页面中更新&#xff…

securecrt连接服务器报错 Key exchange failed 怎么办

新买了一台阿里云机&#xff0c;用securecrt去连接&#xff0c;如下报错这个错误表明你的 SSH 客户端与服务器之间无法就密钥交换方法和主机密钥算法达成一致&#xff0c;导致连接失败。这通常是由于客户端和服务器支持的加密算法集不匹配造成的。 解决方式 编辑服务器的/etc/s…

用协议分层模型实战:从物理层到应用层的STM32协议栈开发

目录 1. 揭开协议栈的神秘面纱:从STM32到分层思维 STM32的硬件优势 本章实战:点亮物理层的第一步 2. 数据链路层:让STM32学会“打包”和“拆包” 以太网帧的那些事儿 实战:解析以太网帧 3. 网络层:让STM32学会“找路” LwIP的快速上手 实战:实现一个简单的Ping …

微服务基础环境搭建-centos7

文章目录1、安装docker1.1、安装步骤1.2、docker常用命令2、安装Nginx3、Docker安装Mysql4、Docker安装Redis5、安装Nacos5.1、Nacos的作用5.2、单体服务安装6、安装RocketMQ服务6.1 MQ的作用6.2 RocketMQ的基础服务架构6.2、安装RocketMQ服务6.3、安装dashboard面板服务6.4、R…

Netty知识点

一、Netty的零拷贝机制 零拷贝的基本理念&#xff1a;避免在用户态和内核态之间拷贝数据&#xff0c;从而降低 CPU 占用和内存带宽的消耗除了系统层面的零拷贝。 1、FileRegion 接口 FileRegion 是 Netty 提供的用于文件传输的接口&#xff0c;它通过调用操作系统的 sendfile 函…

Kafka的基本使用

目录 认识Kafka 消息队列 消息队列的核心概念 核心价值与解决的问题 Kafka ZooKeeper Kafka的基本使用 环境安装 启动zookeeper 启动Kafka 消息主题 创建主题 查询主题 修改主题 发送数据 命令行操作 JavaAPI操作 消费数据 命令行操作 JavaAPI操作 认识Kafka…

Flink2.0学习笔记:Table API SQL

stevensu1/EC0720 表 API 和 SQL# 表 API 和 SQL——用于统一流和批处理 加工。表 API 是适用于 Java、Scala 和 Python 的语言集成查询 API&#xff0c;它 允许组合来自关系运算符的查询&#xff0c;例如 selection、filter 和 join in 一种非常直观的方式。Flink 的 SQL 支…

【 SpringAI核心特性 | Prompt工程 】

1. Prompt 工程 基本概念&#xff1a;Prompt ؜工程又叫提示‏词工程&#xff0c;简单来说&#xff0c;就是输入‌给 AI 的指令。 比如下面‏这段内容&#xff0c;就是提示词&#xff1a; 请问桂林电子科技大学是一个怎么样的学校&#xff1f;1.1 Prompt分类 在 AI ؜对话中…

windows wsl2-06-docker hello world

hello-world 例子 就像其他任何一门语言一样&#xff0c;我们来体验 docker 的 hello world $ docker run hello-world但是报错 :~$ docker run hello-world Unable to find image hello-world:latest locally docker: Error response from daemon: Get "https://registry…

Python知识点4-嵌套循环break和continue使用死循环

一、循环【重点掌握】 1.嵌套循环类似于嵌套if语句 语法&#xff1a; while 表达式1&#xff1a;while 表达式2&#xff1a;语句# 1. # 循环5次&#xff0c;打印0~4 m 0 while m < 5:print(m)m 1 # 循环3次&#xff0c;打印0~2 n 0 while n < 3:print(n)n 1print(&qu…