最近在项目当中使用了一些runtime的知识,主要是参考一些大牛之前写过的文章,copy了一些代码,虽然实现了功能,但是对其中的一些东西还是一知半解,所以才下决定学习一下,并做如下总结,有兴趣的同学可以一起来研究一下。
我们都知道OC基于运行时机制(Runtime),它是一个动态调用过程,就是在编译的时候不去确定真正要调用的函数或方法,只在运行的时候才根据名称确定要调用的函数或方法。
1.消息发送
我们知道调用的本质就是发送消息,所以消息机制就是runtime当中最重要的。
在使用消息发送时我们导入
是不够的,我们需要导入
文件,接下来就可以使用消息发送了。
当我们使用消息发送时,发现会报错,解决办法在这里。
到这里我们就可以正常使用objc_msgSend了
- (void)viewDidLoad {
[super viewDidLoad];
Test *test = [[Test alloc] init];
objc_msgSend(test, @selector(function2));//实例方法
objc_msgSend([Test class], @selector(function1));//类方法
}
打印结果:
我们可以使用消息发送做些什么呢?
我们可以使用消息发送去调用第三方框架或者是系统的私有方法;还可以获得系统有哪些私有方法或者私有属性等;另外我们还可以使用它进行自己框架的搭建。有兴趣的童鞋可以深入的研究一下。
2.方法交换
当我们想要给系统的方法扩充功能时通常会选择使用继承或者是category,当然这些不是我要说的,我要说的是使用runtime的方法交换来进行扩展。
我们以UIFont为例,UIFont的systemFontOfSize:是系统提供设置字体大小的方法,我们用自己的方法对其进行扩展,这里我们新建UIFont的category,代码如下:
+(void)load{
//获取需要进行交换的系统方法
Method systemMethod = class_getClassMethod(self, @selector(systemFontOfSize:));
//获取我们自己的方法,方便后面进行交换
Method swizzedMethod = class_getClassMethod(self, @selector(ks_fontSize:));
//进行方法交换
method_exchangeImplementations(systemMethod, swizzedMethod);
}
//对我们自己的方法进行实现
+ (UIFont *)ks_fontSize:(CGFloat)fontSize{
UIFont *font = [UIFont ks_fontSize:fontSize];
if (font) {
NSLog(@"方法交换成功");
}else{
NSLog(@"方法交换失败");
}
return font;
}
到此我们就已经实现了方法的交换。使用时需要注意以下一点:如果我们在使用时调用ks_fontSize:
方法会造成死循环导致crash,因为我们已经使用ks_fontSize:
方法替换掉了systemFontOfSize:
方法的实现(相当于重写了这个方法),所以调用时依然调用系统方法就可以了。
3.方法添加
要实现动态方法的添加,我们首先需要实现resolveInstanceMethod:
。
当我们调用了一个没有实现的方法的时候会自动调用resolveInstanceMethod:
,这样我们就可以知道哪些方法没有实现,从而动态的添加方法。
当我们调用一个没有实现的方法的时候,会引起crash,就像下面这样:
下面上代码了
+(BOOL)resolveInstanceMethod:(SEL)sel{
//动态添加没有实现的方法
if (sel == @selector(function3)) {
class_addMethod(self, sel, (IMP)functionIMP, "v@:");
/*
这里我们要了解一下class_addMethod()的参数:
class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)的<#IMP imp#>
cls:要添加方法的类
SEL:要添加的方法编号,也就是方法名
IMP:方法实现,函数入口,函数名
types:方法类型
*/
return YES;
}
return [super resolveInstanceMethod:sel];
}
//定义一个函数,函数名可以随便写,只要与resolveInstanceMethod:方法当中class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)的<#IMP imp#>一致即可
void functionIMP(id self, SEL _cmd){
NSLog(@"调用functionIMP %@ %@",self,NSStringFromSelector(_cmd));
}
/*如果我们动态添加的方法带有参数,那么我们需要这样写:
void functionIMP(id self, SEL _cmd, id param){
NSLog(@"调用functionIMP %@ %@ %@",self,NSStringFromSelector(_cmd),param);
}
*/
4.属性添加
我们知道category是不能添加属性的,但是利用runtime的属性关联objc_setAssociatedObject
,我们就可以实现给category动态添加属性,从而为某个对象扩展属性。
这里以UIView为例,创建一个UIView的category。下面上代码:
-(void)setDescribe:(NSString *)describe{
/*
objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)
添加属性,跟某个对象产生关联,添加属性
object:要添加属性的对象
key:我们需要根据key获取关联的对象
value:关联的值
policy:关联策略,关联策略的选择要看你关联的属性类型
*/
objc_setAssociatedObject(self, @"describe", describe, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)describe{
return objc_getAssociatedObject(self, @"describe");
}
这样我们使用UIView实例的时候就可以调用到新添加的describe属性。
到这里基本介绍完毕,本人也处在学习runtime的过程当中,如果哪里有不对希望大家能指出来,文章应该是比较浅显的,以后会做深入一点的学习,到时候再跟大家分享,感谢。这里放上demo;