一. 基本函数

  1. 根据 sel 得到 class 的实例方法
	Method class_getInstanceMethod(Class cls, SEL name)
  1. 根据 sel 得到 class 的函数指针
	IMP class_getMethodImplementation(Class cls, SEL name)
  1. 给 class 添加方法
	class_addMethod(Class cls, SEL name, IMP imp, const char * types)
  1. 替换 class 的 sel 对应的函数指针,返回值为 sel 对应的原函数指针
	class_replaceMethod(Class cls, SEL name, IMP imp, const char * types)
  1. 交换两个 method
	method_exchangeImplementations(Method m1, Method m2)
  1. 直接替换 method 的函数指针
	method_setImplementation(Method method, IMP imp)

二. 主要问题

1. 原子性操作问题

解决方案一般是在 `+(void)load`方法中处理;也可以加锁;还可以在`+(void)initialize`中去做,但是一定要注意继承的问题。

2. 改变范围超出预期

比如你可能会只想修改一个实例的方法,但实际上你修改了所有的实例方法。比如你交换的方法真实的实现是在父类中的,你的修改会影响所有的父类派生出来的类。
例如,直接使用 `method_exchangeImplementations` 方法,考虑下这种情况
	@ B
- (void)case1
{
NSLog(@"case 1 B");
}
@end @interface C: B @property (nonatomic, copy) NSString *x; @end @implementation C
- (void)case2
{
NSLog(@"case2 C %@-%@",[self class],self.x);
} @end - (void)someMethod {
Method a1 = class_getInstanceMethod([C class], @selector(case1));
Method a2 = class_getInstanceMethod([C class], @selector(case2));
method_exchangeImplementations(a1, a2); B *b = [[B alloc] init];
[b case1];
}

会发生什么呢?会 crash ,因为 C 作为 B 的子类并没有实现 case1 方法,方法交换会把 B 的case1 替换成 C 的 case2,后面 [b case1]  其实会执行 void _.._case2(C * self, SEL _cmd) 这个函数,里面调用 x 属性,所以 crash。

为了避免这个错误,一般的做法有,先用 class_addMethod 判断能否添加将要替换的方法,如果可以添加,说明子类原先没有实现此方法,这个方法是在父类中实现的。具体可以看参考1。

RSSwizzlejrswizzle 都避免了这个问题。

3. 可能有命名冲突

比如你交换的方法很可能在别的地方(比如类别里)已经有同样命名的存在了。此时的避免方法可以是直接去替换 Method 里的函数指针,保存原有的函数指针来调用:
		typedef IMP *IMPPointer;

		BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
IMP imp = NULL;
Method method = class_getInstanceMethod(class, original);
if (method) {
const char *type = method_getTypeEncoding(method);
imp = class_replaceMethod(class, original, replacement, type);
if (!imp) {
imp = method_getImplementation(method);
}
}
if (imp && store) { *store = imp; }
return (imp != NULL);
} @implementation NSObject (FRRuntimeAdditions)
+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
return class_swizzleMethodAndStore(self, original, replacement, store);
}
@end

4. 可能会使用不一样的方法参数

比如同样调用原来的函数时,`_cmd`已经不一样了,解决方案可以和上面一致。

5. 类簇类的swizzling

对于 Objective-C 中的一些类簇类,比如 NSNumber、NSArray和NSMutableArray 等,因为这些并不是一个具体的类,而是一个抽象类,如果直接在这些类的内部写个方法通过self class等方式来获取 Class 并做方法交换的话,因为并不能获得其真实的类名,所以会达不到想要的效果。

比如,我们可以通过以下代码来得到NSMutableArray的真实类型:

	object_getClass([[NSMutableArray alloc] init]);
objc_getClass("__NSArrayM");

上面代码中__NSArrayMNSMutableArray的真实类名;

6. 子类方法调用了 super 方法,并且都做了交换

比如下面的例子就会发生循环调用。
	@ A
- (void)log {
NSLog(@"i am a");
} - (void)print {
[self print];
}
@end @ B
- (void)log {
NSLog(@"i am b");
[super log];
} - (void)print {
[self print];
}
@end

下面做一下方法交换,并执行子类的方法。

	- (void)test {
Method a1 = class_getInstanceMethod([A class], @selector(log));
Method a2 = class_getInstanceMethod([A class], @selector(print));
method_exchangeImplementations(a1, a2); Method a3 = class_getInstanceMethod([B class], @selector(log));
Method a4 = class_getInstanceMethod([B class], @selector(print));
method_exchangeImplementations(a3, a4); B *b = [[B alloc] init];
[b print];
}

方法的调用流程(用imp来表示)

	B.log - A.print - B.log....

从而形成了循环的引用。

三. 方法交换的实现

1. 直接修改 Method 的函数指针

参考2中提到的,利用 (一、1)中的方法,额外提供一个变量来存储原始的函数指针,如果需要调用原始方法,就用这个变量来主动设置 sel 参数来防止原始函数用到了_cmd 的情况

2. jrswizzle

主要用到了 method_exchangeImplementations 方法,鲁棒性上做的工作就是先做了 class_addMethod 操作。简单是很简单,然而上面所说的大部分问题他都不能避免。

3. RSSwizzle

主要用到了 class_replaceMethod 方法,避免了子类的替换影响了父类。而且对方法交换加了锁,增强了线程安全。有更多的替换选项。并且,他通过block引入了两个方法互相调用或者子类父类同时交换导致的循环问题。上面的问题几乎都可以避免。

问题是:OSSpinLock 不被建议使用了。

官方文档说他解决了method_exchangeImplementations 的限制:

  1. 只有在 +load 方法中才线程安全
  2. 对没有重载的方法交换会遇到非期望的结果
  3. 交换的方法不能依赖 _cmd 参数 (通过 RSSwizzleInfo结构,保存原始的 selector)
  4. 命名冲突

参考:

1.http://nshipster.cn/method-swizzling/

2.https://blog.newrelic.com/2014/04/16/right-way-to-swizzle/

3.http://yulingtianxia.com/blog/2017/04/17/Objective-C-Method-Swizzling/

4.https://stackoverflow.com/questions/5339276/what-are-the-dangers-of-method-swizzling-in-objective-c

Objective-C 方法交换实践(二) - 方法指针交换的更多相关文章

  1. Objective-C 方法交换实践(三) - Aspects 源码解析

    一.类与变量 AspectOptions typedef NS_OPTIONS(NSUInteger, AspectOptions) { AspectPositionAfter = 0, /// 原方 ...

  2. Objective-C 方法交换实践(一) - 基础知识

    一.Objective-C 中的基本类型 首先看下 Objective-C 的对象模型,每个 Objective-C 对象都是一个指向 Class 的指针.Class 的结构如下: struct ob ...

  3. ios开发runtime学习二:runtime交换方法

    #import "ViewController.h" /* Runtime(交换方法):主要想修改系统的方法实现 需求: 比如说有一个项目,已经开发了2年,忽然项目负责人添加一个功 ...

  4. 通过数组初始化链表的两种方法:指向指针的引用node *&tail和指向指针的指针(二维指针)node **tail

    面试高频题:单链表的逆置操作/链表逆序相关文章 点击打开 void init_node(node *tail,char *init_array) 这样声明函数是不正确的,函数的原意是通过数组初始化链表 ...

  5. 最长公共子序列的C++实现---附二维指针的使用方法

    想了挺久到底第一篇在这儿的博客写什么好,刚好这两天又一次看到动态规划的LCS算法觉得还是有点意思的,就拿来写了写,第一篇博客就发它吧. #include<iostream> #includ ...

  6. 《敏捷软件开发-原则、方法与实践》-Robert C. Martin读书笔记(转)

    Review of Agile Software Development: Principles, Patterns, and Practices 本书主要包含4部分内容,这些内容对于今天的软件工程师 ...

  7. 分配一维动态数组or 二维动态数组的方法以及学习 new 方法or vector

    先来个开胃菜 // 使用new动态分配存储空间 #include<iostream> using std::cout; int main() { // 第1种方式 int *a=new i ...

  8. Golang 方法接收者是值还是指针问题

    对于普通结构体作为接收者,值和指针并没有区别. (以下代码摘抄自Go In Action 中文版) type defaultMatcher struct{} // 方法声明为使用 defaultMat ...

  9. iOS 运行时使用(交换两个方法)

    举例 在创建了如下代码 NSString *str=nil; NSURL *url =[NSURL URLWithString:str]; NSLog(@"%@",url); 但是 ...

随机推荐

  1. ZooKeeper学习之路 (六)ZooKeeper API的简单使用(二)级联删除与创建

    编程思维训练 1.级联查看某节点下所有节点及节点值 2.删除一个节点,不管有有没有任何子节点 3.级联创建任意节点 4.清空子节点 ZKTest.java public class ZKTest { ...

  2. 【MySQL-123】MySQL8.0.12 安装于Win10

    参考blog:MySQL8.0.12 安装及配置 [坑一]输入net start mysql时,MYSQL服务无法启动. 问题:第三步my.ini文件编码错误. 解决方案:https://blog.c ...

  3. docker-4-镜像

    是什么 镜像是一种轻量级.可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件, 它包含运行某个软件所需的所有内容,包括代码.运行时.库.环境变量和配置文件. 1.UnionFS(联合文件 ...

  4. robotframwork接口测试(三)—HttpLibrary.HTTP安装

    1. 安装: 1)可以用命令:pip install robotframework-httplibrary(或pip install  --upgrade robotframework-httplib ...

  5. mongoDB的常用语法

    安装: 到mongodb官网下载安装包或者压缩包:https://www.mongodb.com/download-center?jmp=nav 1.如果是msi包的话则点击按步骤安装,如果是压缩包的 ...

  6. oracle数据库之用户管理

    转载 Oracle创建用户.角色.授权.建表   一.oracle数据库的权限系统分为系统权限与对象权限: 系统权限( database system privilege )可以让用户执行特定的命令集 ...

  7. Linux环境变量及其设置

    简介 环境变量是在操作系统中一个具有特定名字的对象,它包含了一个或多个应用程序将使用到的信息.Linux是一个多用户的操作系统,每个用户登录系统时都会有一个专用的运行环境,通常情况下每个用户的默认的环 ...

  8. PAT——1029. 旧键盘

    旧键盘上坏了几个键,于是在敲一段文字的时候,对应的字符就不会出现.现在给出应该输入的一段文字.以及实际被输入的文字,请你列出肯定坏掉的那些键. 输入格式: 输入在2行中分别给出应该输入的文字.以及实际 ...

  9. Reading Software Defined Traffic Measurement with OpenSketch

    NSDI '13 概要 OpenSketch是一个通用的.抽象的测量框架, 与SDN 网络架构类似, OpenSketch 提出将测量控制层和数据层解耦. 数据层运行设为可动态配置的3阶段流水线, 首 ...

  10. oracle中如何将表缓存到内存中

    oracle快速将表缓存到内存中,使得访问速度加快. 共有2种方法:   1)alter table fisher cache; 2)alter table fisher storage(buffer ...