二十八、method_swizzling

本文由快学吧个人写作,以任何形式转载请表明原文出处。

下文会将method_swizzling简称为m_s。

一、简述m_s利用的思想

有关method_swizzling,就会想到AOP,简单复述下AOP和OOP。

AOP : 面向切面编程。是一种编程思想,核心是要解耦和提高模块的复用率。将每个模块中相同的部分提取出来进行封装,调用的时候直接调用封装好的。

OOP : 面向对象。主要是对各个业务模块进行封装,划分出更清晰的逻辑单元。

二、什么是m_s

简单的说就是交换两个SEL的IMP。如下图。

经常会在hook的时候使用。在原有方法前再进行一些处理,然后再继续执行原有方法。

三、method_swizzling的使用

1. 防止数组越界导致的崩溃

1. 用objectAtIndex取值

如果我在ViewController的ViewDidLoad中写如下的代码,会发生数组越界的崩溃 :

为了不让程序直接发生崩溃,可以对NSArray的objectAtIndex方法做一些优化。

但是我们是不能直接操作NSArray的源码的,所以需要通过之前讲过的分类,来变相的解决这个问题。

创建NSArray的分类,并实现NSArray分类的load方法,其他代码如下图 :

再次运行就不会因为越界的问题崩溃 :

越界返回的是nil,所以ViewController中打印的arr[3]就是null。

2. 用[ ]语法取值

ViewController代码如下,就是将objectAtIndex换成常用的arr[]这样的数组取值方法了。

NSArray(JD)分类中的代码不变,还是对objectAtIndex进行m_s,但是还会崩溃。

这个reason就说明了为什么给数组交换方法的时候cls要填__NSArrayI,还讲明了一点,arr[]这种数组取值方式调用的方法是objectAtIndexedSubscript。所以想要不崩溃,处理方法和上面一样,只不过要交换的方法换成了objectAtIndexedSubscript

代码如下图 :

ViewController不动,NSArray(JD)中代码如下 :

再执行,用arr[]取值,越界也不会崩溃了。

3. 优化一些

如果按照上面那么写,也没有什么问题,但是如果有人就要再手动调用一下NSArray的load方法,那么就会出问题。

因为再调用load的时候,又会进行一次m_s,就相当于又将方法换了回来。为了不影响手动调用load的使用,所以需要将m_s的代码设计成只执行一次 :

在运行就还是正常的了,因为load_images就已经运行过一次load了,自己再调用load,也不会进入dispatch_once里面了。

还可以将m_s的代码提取出来,封装成工具类,这样load里面的代码就不会显得臃肿和凌乱。

则NSArray(JD)里面的代码就可以换成 :

2. 关于类簇简单的说一下

数组是一个类簇,那什么是类簇?

类簇是一种设计模式,类簇中的类利用相同的接口,但是可以有不同的实现。类簇概念链接

OC中常见的类簇如下 :

类名 实际名称
NSArray __NSArrayI
NSMutableArray __NSArrayM
NSDictionary __NSDictionaryI
NSMutableDictionary __NSDictionaryM

3. 关于类和父类的m_s

对于子类来说,会经常调用父类的方法,有的时候可能会用m_s直接对父类的方法做一些优化。那么就会存在一些问题,比如 :

(1). 父类的方法是实现的,子类没有实现这个方法,但是调用了父类的这个方法的实现。并且还进行了m_s。那么就会出现一些问题。举例如下 :

JDKid是JDMan的子类。JDMan实现了study方法,子类没有实现,但是子类调用了study,并且子类还在自己的内部对study进行了m_s操作。

JDMan的代码 :

JDKid的代码 :

ViewController中的代码 :

如果就按照上面的这种,那么在[aMan study]这里必定报错。如下图 :

报错的原因 :

  1. 因为JDMan的study的IMP被换成了JDKid的jdkid_study的IMP,也就是如下图
  1. 所以[aMan study]的时候,调用到的IMP是jdkid_study的实现,也就是上图中红框的部分,红框里的[self jdkid_study]这句代码就有问题了,self这时候是aMan,是JDMan的对象,JDMan中是没有jdkid_study的SEL的,也就是说,aMan根本找不到jdkid_study,所以报错unrecognized selector

修改和思路 :

思路 :

  1. 给子类添加study方法,不要影响父类调用自己的study方法。
  2. 如果不想重写父类的study方法,有想要子类也可以调用到study,并添加自己的一些逻辑。那么就要用m_s。

修改代码如下 :

运行结果 :

(2). 父类和子类都没实现的方法,被用m_s交换了

将JDMan的study方法的实现从JDMan.m中去掉。再运行,结果如下 :

会一直递归。

递归的原因 :

如下图 :

上面的确是给JDKid添加了一个study的方法,并且JDKid的study方法的实现实际上是jdkid_study的实现。所以aKid调用study的时候,实际上是调用了jdkid_study的实现,在jdkid_study中是有一行[self jdkid_study]的,因为JDMan也没实现study,导致在给jdkid_study换IMP实现的时候,图中的oriMethod是nil的,所以根本换不成,那么jdkid_study的实现就还是原来自己的实现。这就变成了递归。

修改和思路 :

判断一下父类是否实现了,如果没实现,在拿到jdkid_study的实现给到JDKid动态添加的study方法后,手动给jdkid_study更换一个IMP实现。

因为JDMan没有study方法,所以在ViewController中就把[aMan study]注释掉,避免崩溃。

ViewController中的代码 :

修改后的代码 :

再运行就是正常的了 :

(3). 小结

m_s不要影响到原来的类的调用,尽可能的是在原有的方法基础上做一些优化。子类如果没有重写方法,还想在父类的方法中做一些事情,那么就要动态的给子类添加这个方法,去做method_swizzling的也是子类的方法交换,不要影响到父类的使用。

如果是交换类方法,则更改如下 :

相关推荐
Magnetic_h8 小时前
【iOS】单例模式
笔记·学习·ui·ios·单例模式·objective-c
归辞...9 小时前
「iOS」——单例模式
ios·单例模式·cocoa
yanling202311 小时前
黑神话悟空mac可以玩吗
macos·ios·crossove·crossove24
归辞...13 小时前
「iOS」viewController的生命周期
ios·cocoa·xcode
crasowas17 小时前
Flutter问题记录 - 适配Xcode 16和iOS 18
flutter·ios·xcode
2401_8524035517 小时前
Mac导入iPhone的照片怎么删除?快速方法讲解
macos·ios·iphone
SchneeDuan17 小时前
iOS六大设计原则&&设计模式
ios·设计模式·cocoa·设计原则
JohnsonXin1 天前
【兼容性记录】video标签在 IOS 和 安卓中的问题
android·前端·css·ios·h5·兼容性
蒙娜丽宁1 天前
Go语言错误处理详解
ios·golang·go·xcode·go1.19
名字不要太长 像我这样就好1 天前
【iOS】push和pop、present和dismiss
学习·macos·ios·objective-c·cocoa