一.交换两个方法的实现:
Runtime还可以交换两个方法的实现。
例如:Person有两个方法:study、run。我们交换两个方法的实现,调用study的时候执行的是run、调用run的时候执行的是study。
Person类:
.h:
@interface Person : NSObject
-(void)study;
-(void)run;
@end
.m类:
@implementation Person
-(void)study{
NSLog(@"study");
}
-(void)run{
NSLog(@"run");
}
@end
controller里面,对person的两个方法的实现进行交换:
#pragma mark 交换两个方法的实现
-(void)exchangeImp{
Person * person = [[Person alloc] init];
//获取两个实例方法
Method studyMethod = class_getInstanceMethod([Person class], @selector(study));
Method runMethod = class_getInstanceMethod([Person class], @selector(run));
//交换两个方法的实现
method_exchangeImplementations(studyMethod, runMethod);
//调用两个方法
[person study];//打印run
[person run];//打印study ,交换成功
}
注意:
获取方法:类方法用class_getClassMethod ,对象方法用class_getInstanceMethod;
使用method_exchangeImplementations即可交换两个方法的实现
二.Method Swizzle
Method Swizzle 被称作黑魔法,就是拦截系统方法,也可以说成对系统的方法进行替换。
为什么叫黑魔法,因为在一个分类里面对一个系统方法的实现进行改变之后,不需要引入任何头文件,使用了这个系统方法的地方,所有实现都会随之改变。
例如,我们可以把所有使用imageNamed:name的方法,加载的image都变为smile.png这张图片。
首先,controller里面添加一张图片,图片上面添加的图片名为test.png
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//黑魔法(Method Swizzle)
//添加一个ImageView到界面上
[self addImageView];
}
-(void)addImageView{
UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 29, 29)];
imageView.center = self.view.center;
[self.view addSubview:imageView];
//imageView的图片名为test.png
imageView.image = [UIImage imageNamed:@"test.png"];
}
@end
新建UIImage分类UIImage+Handle,在load方法里面把imageNamed的实现替换为我们自己写的YYImageNamed的实现。
YYImageNamed里面把图片名改为smile.png.
.h:
#import <UIKit/UIKit.h>
@interface UIImage (Handle)
@end
.m:
#import "UIImage+Handle.h"
#import <objc/runtime.h>
@implementation UIImage (Handle)
+(void)load{
//获取两个方法
Method imageNamedMethod = class_getClassMethod([self class], @selector(imageNamed:));
Method newImageNamedMethod = class_getClassMethod([self class], @selector(YYImageNamed:));
//交换两个方法的实现
method_exchangeImplementations(imageNamedMethod, newImageNamedMethod);
}
+(UIImage *)YYImageNamed:(NSString *)name{
//把所有的图片名字改成smile.png
name = @"smile.png";
return [self YYImageNamed:name];
}
@end
我们在controller里面不需要做任何处理,原本该显示test.png的位置,就变成了smile.png来显示。
1.Method Swizzle怎么做到的呢?
在分类的load方法里面,用YYImageNamed替换系统的imageNamed方法。
2.为什么在load里面替换方法实现?
在oc中,运行时会自动调用每个类的load方法和initialize方法。并且实现了这两个方法才会被调动。
load在类初始加载时被调用;
initialize在第一次调用类的类方法/实例方法之前被调用;
2.1了解load:
1、运行的时候会把所有需要的类加载到内存里面
2、如果类里面实现了load方法,在类加载到内存的时候就会调用load方法,且只调用一次
3、所有类的load方法执行顺序:
1)按照Targets->Build Phases->Compile Source里面的顺序执行;
2)如果有父类,会先调用父类的load方法再调用子类的load方法;
3)一个类别的load方法是在自己的load方法之后执行
2.2了解initialize:
1、当类第一次调用类的类方法/实例方法之前被调用,也只会调用一次。eg:在[NSArray alloc] 的时候就被调用。
2、initialize用于对某一个类的初始化
3、如果存在继承关系,会先调用父类的initialize方法,再调用子类的initialize方法
2.3答案:
从上面我们可以看出,运行时都会调用每个类的load、initialize方法,我们可以从这两个方法里面进行方法交换,保证后面每次调用系统的方法时,系统方法的实现已经是我们改变过的了。
2.4扩展:那为什么要在load方法里面执行方法交换,不在initialize方法里面执行呢?
因为Swizzling影响到类的全局状态,我们要保证资源不能互相竞争。
使用load方法,就可以保证在调用类的任何一个方法之前,就把想要做的事情做了(方法交换)
假如我们在initialize里面进行方法交换:
我们交换的是alloc方法,那一个类第一次被初始化的时候,调用alloc进行初始化。此时系统就会调用initialize,initialize里面才进行方法交换。这样调用的就不是我们交换过的方法了。资源存在竞争问题。就变成我们还没有交换的时候,就已经被调用了。
3.为什么YYImageNamed里面return [self YYImageNamed:name];
,这不会造成循环引用吗?
回顾YYImageNamed里面,我们是这么写的。
+(UIImage *)YYImageNamed:(NSString *)name{
//把所有的图片名字改成smile.png
name = @"smile.png";
return [self YYImageNamed:name];
}
交换原理:
交换之前:
交换之前
交换之后:
交换之后
交换之后,系统的方法名已经变成我们的方法名,所以最后其实调用的是系统的方法,并不会循环。这样就实现了系统方法拦截。
网友评论