Method-Swizzling
- Method-Swizzling即方法的交换,其实现方式是在运行时将一个方法的实现替换成另一个方法的实现,这就是我们常说的iOS黑魔法。
- 每个类都维护着一个方法类表即rwe中的method_array_t,在method_array_t中存储中许多不同的方法即Method,在iOS底层系列16 -- 类的加载机制已经探索了在运行时,这些主类的方法集合和分类的方法集合是如何加载进入主类class rwe结构中的method_array_t当中的。
- 每个Method都包含sel和IMP两个部分,分别表示method的名称和method的实现;方法交换就是将sel和imp原本的对应断开,并将sel和新的IMP生成对应关系。
Method-Swizzling常见函数
-
class_getInstanceMethod
:获取实例方法 -
class_getClassMethod
:获取类方法 -
method_getImplementation
:获取方法的实现 -
method_setImplementation
:设置方法的实现 -
method_getTypeEncoding
:获取方法实现的编码类型 -
class_addMethod
:给方法添加实现 -
class_replaceMethod
:用一个方法的实现,替换另一个方法的实现,即IMP1指向 IMP2,但是IMP2不一定指向IMP1 -
method_exchangeImplementations
:交换两个方法的实现,即IMP1指向IMP2,IMP2指向IMP1
下面通过几个案例来阐述在使用Method-Swizzling过程中需要注意的事项
准备工作:新建两个类父类YYPerson
,子类YYStudent
,Method-Swizzling帮助类YYRuntimeHelper
,YYStudent分类(Add)
#import <Foundation/Foundation.h>
@interface YYPerson : NSObject
- (void)walk;
@end
#import "YYPerson.h"
@implementation YYPerson
- (void)walk{
NSLog(@"%s",__func__);
}
@end
#import <Foundation/Foundation.h>
@interface YYStudent : NSObject
- (void)read;
- (void)write;
@end
#import "YYStudent.h"
@implementation YYStudent
- (void)read{
NSLog(@"%s",__func__);
}
- (void)write{
NSLog(@"%s",__func__);
}
@end
#import "YYStudent.h"
@interface YYStudent (Add)
@end
#import "YYStudent+Add.h"
#import "YYRuntimeHelper.h"
@implementation YYStudent (Add)
//在运行时,进行方法的交换
+ (void)load{
[YYRuntimeHelper yy_instanceMethodSwizzlingithClass:self originalSEL:@selector(read) swizzledSEL:@selector(yy_read)];
}
- (void)yy_read{
NSLog(@" 使用yy_read的方法去实现read");
}
@end
#import <Foundation/Foundation.h>
@interface YYRuntimeHelper : NSObject
+ (void)yy_instanceMethodSwizzlingithClass:(Class)cls originalSEL:(SEL)originalSEL swizzledSEL:(SEL)swizzledSEL;
@end
#import "YYRuntimeHelper.h"
#import <objc/runtime.h>
@implementation YYRuntimeHelper
+ (void)yy_instanceMethodSwizzlingithClass:(Class)cls originalSEL:(SEL)originalSEL swizzledSEL:(SEL)swizzledSEL{
Method originalMethod = class_getInstanceMethod(cls, originalSEL);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSEL);
//方法交换
method_exchangeImplementations(originalMethod, swizzledMethod);
}
@end
案例一:外界主动调用目标类的load方法,导致Method-Swizzling失效
Snip20210312_116.png上面的截图是正常情况;但如果在AppDelegate.m
文件中,主动调用[YYStudent load]
,那么当代码执行到控制器时,Method-Swizzling就失效了,因为前后交换了两次还原了。
解决方案:保证YYStudent(Add)
中load方法中的Method-Swizzling只执行一次,加入dispatch_once
YYStudent(Add)
修改后的代码如下:
#import "YYStudent+Add.h"
#import "YYRuntimeHelper.h"
@implementation YYStudent (Add)
//在运行时,进行方法的交换
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[YYRuntimeHelper yy_instanceMethodSwizzlingithClass:self originalSEL:@selector(read) swizzledSEL:@selector(yy_read)];
});
}
- (void)yy_read{
NSLog(@" 使用yy_read的方法去实现read");
}
@end
案例二:子类没有实现目标方法,但是父类实现了目标方法,在子类的分类中交换目标方法的实现时,最终交换的是父类的实现。
例如上面的YYPerson类有walk的实现,但在子类YYStudent中没有walk的实现;YYStudent分类中的交换实现如下:
#import "YYStudent+Add.h"
#import "YYRuntimeHelper.h"
@implementation YYStudent (Add)
//在运行时,进行方法的交换
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[YYRuntimeHelper yy_instanceMethodSwizzlingithClass:self originalSEL:@selector(walk) swizzledSEL:@selector(yy_walk)];
});
}
- (void)yy_walk{
//这里不会出现循环调用,因为已经实现了方法的交换
[self yy_walk];
NSLog(@" 使用yy_walk的方法去实现walk");
}
@end
外界调用及LLDB调试如下:
Snip20210315_122.png Snip20210315_121.png出现了崩溃,首先因为子类YYStudent没有walk方法实现,所以Method-Swizzling交换的是父类的walk,见下图所示:
Snip20210315_123.png-
[student walk]
由于student没有walk方法实现,会调用父类YYPerson的walk方法,而父类的walk在运行时进行了Method-Swizzling,所以会调用YYStudent(Add)中的yy_walk
方法实现,其内部首先调用[self yy_walk]
,此时的self是student,调用的则是父类YYPerson的walk方法实现; -
[person walk]
则调用了YYStudent(Add)中的yy_walk
方法实现,其内部首先调用[self yy_walk]
此时的self是person,而YYPerson类中并没有yy_walk方法,所以会出现崩溃。 - 本意是交换子类的walk实现,不想将父类的walk实现也交换了,且会发生崩溃;为了Method-Swizzling不影响父类该怎么做?
解决方案:在进行方法交换时,首先判断当前类是否有目标方法,如果有直接进行交换;如果没有调用class_addMethod
函数,给当前类动态添加一个方法(目标方法),但其实现是交换方法的实现;添加成功之后再调用class_replaceMethod
将交换方法的实现替换成目标方法的实现;这样才完整的实现了Method-Swizzling
YYRuntimeHelper
修改后的代码如下:
#import "YYRuntimeHelper.h"
#import <objc/runtime.h>
@implementation YYRuntimeHelper
+ (void)yy_instanceMethodSwizzlingithClass:(Class)cls originalSEL:(SEL)originalSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) {
return;
}
Method original_method = class_getInstanceMethod(cls, originalSEL);
Method swizzled_method = class_getInstanceMethod(cls, swizzledSEL);
IMP original_imp = method_getImplementation(original_method);
IMP swizzled_imp = method_getImplementation(swizzled_method);
//主要用来检测当前类是否有original_method,避免替换了父类的实现
//originalSEL --> swizzled_imp
BOOL success = class_addMethod(cls, originalSEL, swizzled_imp, method_getTypeEncoding(original_method));
//添加成功,表示当前类没有original_method
if (success) {
//swizzledSEL --> original_imp
//因为当前类没有original_method 这里的original_imp是当前类父类的实现
class_replaceMethod(cls, swizzledSEL, original_imp, method_getTypeEncoding(original_method));
}else{//添加失败,表示当前类有original_method
//方法交换
method_exchangeImplementations(original_method,swizzled_method);
}
}
@end
- 注意这里的
original_imp
是父类的实现,因为当前类没有original_method
LLDB调试结果如下:
Snip20210312_120.png案例三:子类没有实现目标方法,且父类也没有实现目标方法。
外界调用及LLDB调试结果如下:
Snip20210315_124.png Snip20210315_125.pngMethod-Swizzling的代码实现:
Snip20210315_126.png- 首先运行时进行Method-Swizzling,由于当前类YYStudent没有walk方法,所以会调用
class_addMethod
函数,给YYStudent添加walk方法其实现为yy_walk
;添加成功后会执行class_replaceMethod
函数,但由于original_imp不存在,所以会交换失败;那么Method-Swizzling只实现了单方的交换,如下图所示:
- 当
[student walk]
时,会来到yy_walk
方法,当执行[self yy_walk]
时,由于yy_walk并没有交换成功,所以调用的实现还是自己本身,则出现循环调用,最后导致内存溢出应用崩溃。
解决方案:在进行Method-Swizzling之前,对原始方法进行检测,若为空调用class_addMethod
函数,添加原始方法walk,方法实现为交换方法实现yy_walk;然后将交换方法yy_walk的实现置为{},即为空方法什么也不做,接着再调用class_addMethod
函数,此时上面已经添加过了,所以再次添加会不成功,最后走method_exchangeImplementations
函数,但由于original_method是空的,所以不会发生交换,最终的效果如下所示:
YYRuntimeHelper
修过之后的代码如下:
+ (void)yy_instanceMethodSwizzlingithClass:(Class)cls originalSEL:(SEL)originalSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) {
return;
}
Method original_method = class_getInstanceMethod(cls,originalSEL);
Method swizzled_method = class_getInstanceMethod(cls,swizzledSEL);
IMP original_imp = method_getImplementation(original_method);
IMP swizzled_imp = method_getImplementation(swizzled_method);
if (!original_method) {
//在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现
class_addMethod(cls,originalSEL,swizzled_imp, method_getTypeEncoding(swizzled_method));
method_setImplementation(swizzled_method, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
}
BOOL didAddMethod = class_addMethod(cls,originalSEL,swizzled_imp, method_getTypeEncoding(swizzled_method));
if (didAddMethod) {
class_replaceMethod(cls,swizzledSEL,original_imp, method_getTypeEncoding(original_method));
}else{
method_exchangeImplementations(original_method,swizzled_method);
}
}
method_exchangeImplementations
源码实现如下:
Method-Swizzling之类方法
上面介绍的是实例方法的交换,类方法的交换原理是类似的,只不过类方法存储在元类,具体实现代码如下:
+ (void)yy_classMethodSwizzlingithClass:(Class)cls originalSEL:(SEL)originalSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) {
return;
}
class_getClassMethod(cls,originalSEL);
Method original_method = class_getClassMethod([cls class],originalSEL);
Method swizzled_method = class_getClassMethod([cls class],swizzledSEL);
IMP original_imp = method_getImplementation(original_method);
IMP swizzled_imp = method_getImplementation(swizzled_method);
if (!original_method) {
//在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现
//object_getClass(cls) 获取元类
class_addMethod(object_getClass(cls),originalSEL,swizzled_imp, method_getTypeEncoding(swizzled_method));
method_setImplementation(swizzled_method,
imp_implementationWithBlock(^(id self,SEL _cmd){
NSLog(@"来了一个空的 imp");
}));
}
BOOL didAddMethod = class_addMethod(object_getClass(cls),originalSEL,swizzled_imp, method_getTypeEncoding(swizzled_method));
if (didAddMethod) {
class_replaceMethod(object_getClass(cls),swizzledSEL,original_imp, method_getTypeEncoding(original_method));
}else{
method_exchangeImplementations(original_method,swizzled_method);
}
}
网友评论