Runtime学习浅谈

最近在项目当中使用了一些runtime的知识,主要是参考一些大牛之前写过的文章,copy了一些代码,虽然实现了功能,但是对其中的一些东西还是一知半解,所以才下决定学习一下,并做如下总结,有兴趣的同学可以一起来研究一下。
我们都知道OC基于运行时机制(Runtime),它是一个动态调用过程,就是在编译的时候不去确定真正要调用的函数或方法,只在运行的时候才根据名称确定要调用的函数或方法。

1.消息发送

我们知道调用的本质就是发送消息,所以消息机制就是runtime当中最重要的。
在使用消息发送时我们导入是不够的,我们需要导入文件,接下来就可以使用消息发送了。

当我们使用消息发送时,发现会报错,解决办法在这里。

消息发送.png

到这里我们就可以正常使用objc_msgSend了

- (void)viewDidLoad {
    [super viewDidLoad];

    Test *test = [[Test alloc] init];

    objc_msgSend(test, @selector(function2));//实例方法
    
    objc_msgSend([Test class], @selector(function1));//类方法
}

打印结果:
打印结果.png

我们可以使用消息发送做些什么呢?
我们可以使用消息发送去调用第三方框架或者是系统的私有方法;还可以获得系统有哪些私有方法或者私有属性等;另外我们还可以使用它进行自己框架的搭建。有兴趣的童鞋可以深入的研究一下。

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,就像下面这样:


动态添加function.png

下面上代码了

+(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;

你可能感兴趣的:(Runtime学习浅谈)