前言:
Method Swizzling 是什么
Method Swizzling是objective-c中的黑魔法,算是runtime中的一种实战使用模式,它允许我们动态的替换方法,实现Hook功能。
但是它也是一把双刃剑,用得好的人可以用它来很轻松的实现一些复杂的功能,而如果用的不好,后果就真的是毁灭性的伤害,这样的黑魔法,我们一定要尽力去掌握并驾驭它。
Method Swizzling 能做什么
先从名字来看,Method方法Swizzling混合,那他的意思就是方法混合??? 好像也没有一个准确的翻译,我们就姑且翻译成方法交换吧。
也就是说把原来 A方法实现的a,原来B方法实现的b交换一下,让A来实现b的功能,让B来实现a的功能。咋一看好像没什么厉害的地方,不就是交换个方法么,有什么用呢?您先别急,往下看。
Method Swizzling 原理
在Method方法中,有两个关键的成员变量:SEL和IMP。
IMP是一个函数指针,指向的是方法的实现。
原则上,方法名SEL和IMP是一一对应的,那Method Swizzling的本质就是改变他们的对应关系,达到交换方法实现的目的。
交换方法的坑点和注意事项
坑点:
本类Cls中并没有实现要交换的方法,但是父类中存在该方法
类A(run) -> 继承 类B(eat)
当A去实现交换 run 和 eat 的时候,并不会出现问题 ; 一旦B去实现自己原本有的 eat的时候,这时候就大概率出现问题, !!!!!
例子说明:
//
// Test3ViewController.m
// TestDemo
//
// Created by jiangbin on 2021/1/6.
// Copyright © 2021 ice. All rights reserved.
//
#import "Test3ViewController.h"
#import <objc/runtime.h>
@interface B : NSObject
@end
@implementation B
- (void)eat{
NSLog(@"%@ eat ----",self);
}
@end
@interface A : B
@property (nonatomic , copy)NSString* name;
@end
@implementation A
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method oriMethod = class_getInstanceMethod(self, @selector(run));
Method swiMethod = class_getInstanceMethod(self, @selector(eat));
method_exchangeImplementations(oriMethod, swiMethod);
});
}
- (void)run{
NSLog(@ " %@ run ----",self);
NSLog(@"%@",self.name);
}
@end
@interface Test3ViewController ()
@end
@implementation Test3ViewController
- (void)viewDidLoad {
[super viewDidLoad];
A* a = [A new];
[a eat];
B* b = [B new];
[b eat];
// Do any additional setup after loading the view.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
运行结果
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[B name]: unrecognized selector sent to instance 0x600002fe82c0'
terminating with uncaught exception of type NSException
原因分析:
A类将自己的方法run 和 父类的方法eat 进行了调换,A调用eat的时候自然会走自己原有的run方法,此时是没有错误的,但是B调用自己子类的run方法,一旦该方法中出现父类不曾有的属性或者方法时,程序就会出现异常崩溃
补救措施:
{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
// 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
// 交换自己没有实现的方法:
// 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
// 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL
BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
if (success) {// 自己没有 - 交换 - 没有父类进行处理 (重写一个)
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{ // 自己有
method_exchangeImplementations(oriMethod, swiMethod);
}
}
method-swizzling的应用:
method-swizzling最常用的应用是防止数组、字典等越界崩溃
在iOS中NSNumber、NSArray、NSDictionary等这些类都是类簇
,一个NSArray的实现可能由多个类组成。所以如果想对NSArray进行Swizzling,必须获取到其“真身”进行Swizzling,直接对NSArray进行操作是无效的。
防止数组越界:
+ (void)load{
Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(custom_objectAtIndex:));
method_exchangeImplementations(fromMethod, toMethod);
}
...做自己的处理(debug和release模式要区分开来)
网友评论