method-swizzling
的含义是方法交换
,其主要作用是在运行时将一个方法的实现替换成另一个方法的实现
,这就是我们常说的iOS黑魔法
,
在OC中就是利用method-swizzling实现AOP
,其中AOP
(Aspect Oriented Programming,面向切面编程)是一种编程的思想,区别于OOP(面向对象编程)
OOP
编程思想更加倾向于对业务模块的封装
,划分出更加清晰的逻辑单元;AOP
是面向切面进行提取封装,提取各个模块中的公共部分,提高模块的复用率,降低业务之间的耦合性
。每个类都维护着一个方法列表
,即methodList
,methodList
中有不同的方法
即Method
,每个方法中包含了方法的sel
和IMP
,方法交换就是将sel和imp原本的对应断开,并将sel和新的IMP生成对应关系
如下图所示,交换前后的sel和IMP的对应关系
通过sel
获取方法Method
class_getInstanceMethod
:获取实例方法
class_getClassMethod
:获取类方法
method_getImplementation
:获取一个方法的实现
method_setImplementation
:设置一个方法的实现
method_getTypeEncoding
:获取方法实现的编码类型
class_addMethod
:添加方法实现
class_replaceMethod
:用一个方法的实现,替换另一个方法的实现,即aIMP 指向 bIMP
,但是bIMP不一定指向aIMP
method_exchangeImplementations
:交换两个方法的实现,即 aIMP -> bIMP, bIMP -> aIMP
所谓的一次性就是:mehod-swizzling
写在load
方法中,而load
方法会主动调用多次
,这样会导致方法的重复交换
,使方法sel的指向又恢复成原来的imp的问题
解决方案
可以通过单例设计
原则,使方法交换只执行一次
,在OC中可以通过dispatch_once
实现单例
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_bestMethodSwizzlingWithClass:self oriSEL:@selector(helloword) swizzledSEL:@selector(lg_studentInstanceMethod)];
});
}
在下面这段代码中,LGPerson
中实现了personInstanceMethod
,而LGStudent
继承自LGPerson
,没有实现personInstanceMethod
,运行下面这段代码会出现什么问题?
//*********LGPerson类*********
@interface LGPerson : NSObject
- (void)personInstanceMethod;
@end
@implementation LGPerson
- (void)personInstanceMethod{
NSLog(@"person对象方法:%s",__func__);
}
@end
//*********LGStudent类*********
@interface LGStudent : LGPerson
- (void)helloword;
+ (void)sayHello;
@end
@implementation LGStudent
@end
//*********调用*********
- (void)viewDidLoad {
[super viewDidLoad];
// 黑魔法坑点二: 子类没有实现 - 父类实现
LGStudent *s = [[LGStudent alloc] init];
[s personInstanceMethod];
LGPerson *p = [[LGPerson alloc] init];
[p personInstanceMethod];
}
其中,方法交换代码如下,是通过LGStudent
的分类LG
实现
@implementation LGStudent (LG)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)];
});
}
// personInstanceMethod 我需要父类的这个方法的一些东西
// 给你加一个personInstanceMethod 方法
// imp
- (void)lg_studentInstanceMethod{
是否会产生递归?--不会产生递归,原因是lg_studentInstanceMethod 会走 oriIMP,即personInstanceMethod的实现中去
[self lg_studentInstanceMethod];
NSLog(@"LGStudent分类添加的lg对象方法:%s",__func__);
}
@end
下面是封装好的method-swizzling
方法
@implementation LGRuntimeTool
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
method_exchangeImplementations(oriMethod, swiMethod);
}
通过实际代码的调试,发现会在p调用personInstanceMethod
方法时崩溃,下面来进行详细说明
[s personInstanceMethod];
中不报错是因为 student
中的imp
交换成了lg_studentInstanceMethod
,而LGStudent
中有这个方法(在LG分类中),所以不会报错
崩溃的点在于[p personInstanceMethod];
,其本质原因:LGStudent
的分类LG
中进行了方法交换
,将person
中imp
交换成了 LGStudent
中的lg_studentInstanceMethod
,然后需要去 LGPerson
中的找lg_studentInstanceMethod
,但是LGPerson
中没有lg_studentInstanceMethod
方法,即相关的imp找不到
,所以就崩溃了
优化:避免imp找不到
通过class_addMethod
尝试添加你要交换的方法
添加成功
,即类中没有这个方法,则通过class_replaceMethod
进行替换
,其内部会调用class_addMethod
进行添加method_exchangeImplementations
进行交换
+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
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);
}
}
下面是class_replaceMethod
、class_addMethod
和method_exchangeImplementations
的源码实现
其中class_replaceMethod
和class_addMethod
中都调用了addMethod
方法,区别在于bool值的判断,下面是addMethod
的源码实现
在调用personInstanceMethod
方法时,父类LGPerson中只有声明,没有实现,子类LGStudent中既没有声明,也没有实现
//*********LGPerson类*********
@interface LGPerson : NSObject
- (void)personInstanceMethod;
@end
@implementation LGPerson
@end
//*********LGStudent类*********
@interface LGStudent : LGPerson
- (void)helloword;
+ (void)sayHello;
@end
@implementation LGStudent
@end
//*********调用*********
- (void)viewDidLoad {
[super viewDidLoad];
// 黑魔法坑点二: 子类没有实现 - 父类实现
LGStudent *s = [[LGStudent alloc] init];
[s personInstanceMethod];
LGPerson *p = [[LGPerson alloc] init];
[p personInstanceMethod];
}
经过调试,发现运行代码会崩溃,报错结果如下所示
原因是 栈溢出
,递归死循环
了,那么为什么会发生递归呢?----主要是因为 personInstanceMethod
没有实现,然后在方法交换时,始终都找不到oriMethod,然后交换了寂寞,即交换失败,当我们调用personInstanceMethod(oriMethod)
时,也就是oriMethod
会进入LG中lg_studentInstanceMethod
方法,然后这个方法中又调用了lg_studentInstanceMethod
,此时的lg_studentInstanceMethod
并没有指向oriMethod
,然后导致了自己调自己
,即递归死循环
优化:避免递归死循环
oriMethod
为空,为了避免方法交换没有意义,而被废弃,需要做一些事情
通过class_addMethod
给oriSEL
添加swiMethod
方法
通过method_setImplementation
将swiMethod
的IMP
指向不做任何事的空实现
+ (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
if (!oriMethod) {
// 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
}
// 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
// 交换自己没有实现的方法:
// 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
// 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL
//oriSEL:personInstanceMethod
BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{
method_exchangeImplementations(oriMethod, swiMethod);
}
}
类方法和实例方法的method-swizzling的原理是类似的,唯一的区别是类方法存在元类中,所以可以做如下操作
LGStudent
中只有类方法sayHello
的声明,没有实现@interface LGStudent : LGPerson
- (void)helloword;
+ (void)sayHello;
@end
@implementation LGStudent
@end
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_bestClassMethodSwizzlingWithClass:self oriSEL:@selector(sayHello) swizzledSEL:@selector(lg_studentClassMethod)];
});
}
+ (void)lg_studentClassMethod{
NSLog(@"LGStudent分类添加的lg类方法:%s",__func__);
[[self class] lg_studentClassMethod];
}
类方法的方法交换
如下
需要通过class_getClassMethod
方法获取类方法
在调用class_addMethod
和class_replaceMethod
方法添加和替换时,需要传入的类是元类
,元类可以通过object_getClass
方法获取类的元类
//封装的method-swizzling方法
+ (void)lg_bestClassMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getClassMethod([cls class], oriSEL);
Method swiMethod = class_getClassMethod([cls class], swizzledSEL);
if (!oriMethod) { // 避免动作没有意义
// 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
class_addMethod(object_getClass(cls), oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
NSLog(@"来了一个空的 imp");
}));
}
// 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
// 交换自己没有实现的方法:
// 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
// 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL
//oriSEL:personInstanceMethod
BOOL didAddMethod = class_addMethod(object_getClass(cls), oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
if (didAddMethod) {
class_replaceMethod(object_getClass(cls), swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{
method_exchangeImplementations(oriMethod, swiMethod);
}
}
- (void)viewDidLoad {
[super viewDidLoad];
[LGStudent sayHello];
}
方法没有实现
,所以会走到空的imp
中method-swizzling最常用的应用是防止数组、字典等越界崩溃
在iOS中NSNumber
、NSArray
、NSDictionary
等这些类都是类簇,一个NSArray
的实现可能由多个类组成
。所以如果想对NSArray进行Swizzling,必须获取到其“真身”进行Swizzling,直接对NSArray进行操作是无效的
。
下面列举了NSArray和NSDictionary本类的类名,可以通过Runtime函数取出本类。
类名 | 真身 |
---|---|
NSArray | __NSArrayI |
NSMutableArray | __NSArrayM |
NSDictionary | __NSDictionaryI |
NSMutableDictionary | __NSDictionaryM |
以 NSArray 为例
CJLArray
@implementation NSArray (CJLArray)
//如果下面代码不起作用,造成这个问题的原因大多都是其调用了super load方法。在下面的load方法中,不应该调用父类的load方法。这样会导致方法交换无效
+ (void)load{
Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(cjl_objectAtIndex:));
method_exchangeImplementations(fromMethod, toMethod);
}
//如果下面代码不起作用,造成这个问题的原因大多都是其调用了super load方法。在下面的load方法中,不应该调用父类的load方法。这样会导致方法交换无效
- (id)cjl_objectAtIndex:(NSUInteger)index{
//判断下标是否越界,如果越界就进入异常拦截
if (self.count-1 < index) {
// 这里做一下异常处理,不然都不知道出错了。
#ifdef DEBUG // 调试阶段
return [self cjl_objectAtIndex:index];
#else // 发布阶段
@try {
return [self cjl_objectAtIndex:index];
} @catch (NSException *exception) {
// 在崩溃后会打印崩溃信息,方便我们调试。
NSLog(@"---------- %s Crash Because Method %s ----------\n", class_getName(self.class), __func__);
NSLog(@"%@", [exception callStackSymbols]);
return nil;
} @finally {
}
#endif
}else{ // 如果没有问题,则正常进行方法调用
return [self cjl_objectAtIndex:index];
}
}
@end
NSArray *array = @[@"1", @"2", @"3"];
[array objectAtIndex:3];
文章浏览阅读1.6k次。问题 A: 你的名字题目描述Walfy同时喜欢上了n个妹子,第i个妹子的智力值为ai魅力值为bi。为了防止妹子们发现walfy是个渣男,现在walfy要给妹子排个序,这样他能按照这个排序分配和妹子聊天的时间。排序规则如下:智力高的妹子需要花费更多的时间来聊天,如果智力一样,那么魅力高的花费的时间将会多一点;如果她们的魅力也一样,姓名字典序更小的花费的时间也将多一点。对于字典序大小的定义,..._ulire 最近在研究哈夫曼树,一种功能是将一串字符压缩为更短的 01 串的数据结构,现
文章浏览阅读2.3k次。一一配置环境一、安装Miniconda二、下载d21-zh安装包三、用conda创建虚拟环境四、jupyter notebook运行No moudule name'mxnet'一、安装Miniconda二、下载d21-zh安装包三、用conda创建虚拟环境#配置清华PyPI镜像(如无法运行,将pip版本升级到>=10.0.0)pip config set global.index-..._no module named 'd21
文章浏览阅读170次。Paper Reading:Single-Shot Refinement Neural Network for Object Detection_CVPR2018RefineDet论文阅读_single-shot refinement neural network for object detection
文章浏览阅读1.1k次。 接着上一节的内容,这一讲着重介绍下elasticsearch使用head打开和关闭索引.....打开/关闭索引接口允许关闭一个打开的索引或者打开一个已经关闭的索引。关闭的索引只能显示索引元数据信息,不能够进行读写操作。关闭索引的两种方式,第一种是比较low的操作,不建议使用........比如我们新建一个索引student2第一种:关闭索引:我们用 PO..._elasticsearch关闭索引刷新
文章浏览阅读4.9k次,点赞21次,收藏179次。前言:积分梳状滤波器(CIC,Cascaded Integrator Comb),一般用于数字下变频(DDC)和数字上变频(DUC)系统。CIC 滤波器结构简单,没有乘法器,只有加法器、积分器和寄存器,资源消耗少,运算速率高,可实现高速滤波,常用在输入采样率最高的第一级,在多速率信号处理系统中具有着广泛应用。1. DDC 原理DDC 主要由本地振荡器(NCO) 、混频器、滤波器等组成,如下图所示。DDC 将中频信号与振荡器产生的载波信号进行混频,信号中心频率被搬移,再经过抽取滤波,恢复原始信号,实现_cic截止频率
文章浏览阅读1.3k次。1 #include<iostream> 2 3 using namespace std; 4 5 bool isPrime(int n) { 6 for (int i = 2; i < n; i++) { 7 if (n%i == 0) { //能被2到把自身小1的数整除的都不是素数 8 ..._分解偶数描述任何一个大于2的偶数都可以分解为两个素数的和,而且可能有多种分
文章浏览阅读4k次。前言: 前一段时间,帮女朋友整理她们公司的破Excel文档,本着减少工作量的原则(居家好男人),帮忙写了个java main去读取整理Excel,到后来发现在读取到xlsx的excel报错,报错信息居然没看懂。。。报错信息Exception in thread "main" cn.hutool.poi.exceptions.POIException: IOException: OPC Co..._exception in thread "main" cn.hutool.poi.exceptions.poiexception: ioexceptio
文章浏览阅读3.2k次。Beyond Compare_beyond compare 4.2 密钥
文章浏览阅读1.5k次。前路艰难,但谨记,你并不孤独。Spring Cloud如火如荼,抽空研究研究Spring大家族中的新份子。具体的介绍不会粗线在本系列博文中,如需要理论等知识直接百度or谷歌。Spring Cloud中保护N多已构建好的微服务,可以做到即插即用,其中大致包含几种服务:Config、Eureka、Ribbon、Hystrix、Feign、Bus等,具体介绍及开源地址请见:Spring Cloud中文官_springcloud 集中配置管理
文章浏览阅读116次。题目大意给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被计为是不同的子串。示例 1:输入: "abc"输出: 3解释: 三个回文子串: "a", "b", "c".示例 2:输入: "aaa"输出: 6说明: 6个回文子串: "a", "a", "a", "aa", "aa", "aaa".解题思路动态规划,判断s[i]~s[j]是否是回文串,如果是回文串,res++。class Solution {_leetcode 647
文章浏览阅读4.4k次。Akka中通过下面的方法向actor发送消息! tell 意味着 “fire-and-forget”,即异步的发送消息无需等待返回结果? ask 异步发送消息并返回代表可能回复的Future。消息在每个发件人的基础上是有序的。MailBoxAkka邮箱包含发往Actor的消息。通常每个Actor都有自己的邮箱,但是也有例外,比如BalancingPool所有路由将共享_actor mailbox 实现原理
文章浏览阅读1.1k次。listview是一个以垂直方式在项目中显示视图的列表。是一种不能实现确定视图中的内容的适配器视图(adapter view)。数据和视图的绑定,需要通过继承ListViewAdapter接口的适配器实现。确保当上下滚动的时候,能够动态刷新视图内容。通常我们都会自定义一个继承自BaseAdapter(已继承ListViewAdapter),ArrayAdapter(继承自BaseAdapter),SimpleAdapter(继承自BaseAdapter)的类,重写getView()方法,实现自己想要的功能。_android listview使用