Runtime详解(二)Method Swizzling

本文用来介绍ios开发中【Runtime】中的黑魔法Method Swizzing。通过本文,您将了解到:

1.Method Swizzling(动态方法交换)简介

2.Method Swizzling 使用方法(四种方案)

3.Method Swizzling 使用注意

4.Method Swizzling 应用场景

​ 4.1全局页面统计功能

​ 4.2字体根据屏幕尺寸适配

​ 4.3处理按钮重复点击

​ 4.4TableView、CollectionView 异常加载占位图

​ 4.5APM(应用性能管理)、防止崩溃

我们之前了解了运行机制(Runtime系统)的工作原理,包括消息发送以及转发机制的原理和流程。

从这一篇文章开始,我们来了解一下Runtime在实际开发过程中,具体的应用场景。

这一篇我们来学习一下被称为Runtime运行时系统中最具争议的黑魔法:Method Swizzling(动态方法交换).

1.Method Swizzling(动态方法交换) 简介

Method Swizzling 用于改变一个已经存在的selector实现。我们可以在程序运行时,通过改变selector所在Class(类)的method list(方法列表)的映射从而改变方法的调用。其实质就是交换两个方法的IMP(方法实现)。

上一篇文章我们知道:Method(方法)对应的是objc_method 结构体;而objc_method 结构体 中包含了SEL method_name(方法名)、IMP method_imp (方法实现)。

//  objc_method 结构体
typedef struct objc_method {
   
	SEL _Nonnull method_name; // 方法名
	char * _Nullable method_types;  //. 方法类型
	IMP _Nonnull method_imp;  //  方法实现
}

Method(方法)、SEL(方法名)、IMP(方法实现)三者的关系可以这样来表示:

在运行时,Class(类)维护了一个method list(方法列表)来确定消息的正确发送。method list(方法列表)存放的元素就是Method(方法)。而Method(方法)中映射了一对键值对:SEL(方法名) : IMP(方法实现)。

Method swizzling 修改了method list(方法列表),使得不同Method(方法)中的键值对发生了交换。比如交换前两个键值对分别为SEL A : IMP A、SEL B : IMP B,交换之后就变为了SEL A : IMP B、SEL B :IMP A.如图所示:

在这里插入图片描述

2.Method Swizzling 使用方法

假如当前类中有两个方法:- (void)originalFunction;和- (void)swizzledFunction;.如果我们想要交换两个方法的实现,从而实现调用- (void)originalFunction;方法实际上调用的是- (void)swizzledFunction;方法,而调用B方法实际上是调用A方法的效果。那我们需要像下边代码一样来实现。

2.1 Method Swizzling 简单使用

在当前类的 + (void)load;方法中增加Method Swizzling 操作,交换 - (void)A 和- (void)B的方法实现。

#import "ViewController.h"
#import <objc/runtime.h>

@interface ViewController ()
  
 @end
  
  @implementation ViewController 
  
  - (void)viewDidLoad {
  	[super viewDidLoad];
  	
  	[self SwizzlingMethod];
  	[self A];
  	[self B];
}

// 交换 原方法 和 替换方法 的方法实现
- (void)SwizzlingMethod {
  Class class = [self class];
  SEL A = @selector(A);
  SEL B = @selector(B);
  
  // 原方法结构体 和替换方法结构体
  Method A = class_getInstanceMethod(class, A);
  Method B = class_getInstanceMethod(class, B);
  
  // 调用交换两个方法的实现
  method_exchangeImplementations(A, B);
}

// 原始方法
- (void)A {
  	NSLog(@"A");
}

- (void)B {
  NSLog(@"B");
}
/*打印结果:
  09:59:19.672349+0800 Runtime-MethodSwizzling[91009:30112833] B
  09:59:20.414930+0800 Runtime-MethodSwizzling[91009:30112833] A
 */

刚才我们简单演示了如何当前类中如何进行Method Swizzling 操作。但一般日常开发中,并不是直接在原有类中进行Method Swizzling 操作。更多的是为当前类添加一个分类,然后在分类中进行Method Swizzling操作。另外真正使用会比上边写的考虑东西要多一点,要复杂一些。

在日常使用Method Swizzling 的过程中,有几种很常用的方案,具体情况如下。

2.3 Method Swizzling 方案A(在该类的分类中添加 Method Swizzling 交换方式,用普通方式)

这种方式在开发中应用最多的。但是还是要注意一些事项,我会在接下来的3.Method Swizzling 使用注意 进行详细说明。

@implementation UIViewController (Swizzling)
//. 交换 原方法 和 替换方法 的方法实现
+void)load {
   
	static dispatch_once_t onceToken;
	dispatch_once(&onceToken, ^{
   
	//. 当前类
	Class class = [self class];
    
    // 原方法名 和替换方法名
    SEL ASelector = @selector(A);
    SEL BSelector = @selector(B);
    
    //原方法结构体 和 替换方法结构体
    Method A = class_getInstanceMethod(class, ASelector);
    Method B = class_getInstanceMethod(class, BSelector);
    
    /*
    	如果当前类没有原方法的IMP。说明从父类继承过来的方法实现,
    	需要在当前类中添加一个 ASelector方法,
    	但是用 替换方法 B 去实现它
    */
    
    BOOL didAddMethod = class_addMethod(class, ASelector, method_getImplementation(B),
                                       method_getTypeEncoding(B));
    if (didAddMethod) {
   
      //. 原方法的 IMP 添加成功后, 修改 替换方法的 IMP 为原始方法的 IMP
      class_replaceMethod(class, BSelector, method_getImplementation(A),
                         method_getTypeEncoding(A));
      else {
   
        // 添加失败 (说明已包含原方法的 IMP),调用交换两个方法的实现
        method_exchangeImplementations(A, B);
      }
    }
	});
}

// 原始方法
- (void)A {
   
   NSLog(@"A");
}

- (void)B {
   
  NSLog(@"B");
}

@end

2.2 Method Swizzling 方案 B(在该类的分类中添加 Method Swizzling 交换方法,但是使用函数指针的方式)。

方案B和方案A的最大不同之处在于使用了函数指针的方式,使用函数指针最大的好处是可以有效避免命名错误。

#import "UIViewController+PointerSwizzling.h"
#import <objc/runtime.h>

typedef IMP *IMPPointer;

// 交换方法函数
static void MethodSwizzle(id self, SEL _cmd, id arg1);
// 原始方法函数指针
static void (*MethodOriginal)(id self, SEL _cmd, id arg1);

// 交换方法函数
static void MethodSwizzle(id self, SEL _cmd, id arg1) {
    
    // 在这里添加 交换方法的相关代码
    NSLog(@"swizzledFunc");
    
    MethodOriginal(self, _cmd, arg1);
}

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 UIViewController (PointerSwizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzle:@selector(originalFunc) with:(IMP)MethodSwizzle store:(IMP *)&MethodOriginal];
    });
}

+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
    return class_swizzleMethodAndStore(self, original, replacement, store);
}

// 原始方法
- (void)originalFunc {
    NSLog(@"originalFunc");
}

@end

2.4 Method Swizzling 方案 C(在其他类中添加 Method Swizzling)交换方法

这种情况一般用的不多,最出名的就是AFNetworking 中的 _AFURLSessionTaskSwizzling 私有类。 _AFURLSessionTaskSwizzling 主要解决了ios7和ios8系统上NSURLSession差别的处理。让不同系统版本NSURLSession版本基本一致。

tatic inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
   
    Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值