Runtime经常使用的几个场景

1.给分类动态添加属性

FDFullscreenPopGesture中给UIViewController的分类里有这么一个属性:docker

@property (nonatomic, copy) _FDViewControllerWillAppearInjectBlock fd_willAppearInjectBlock;

这是一个block的属性,block定义以下:安全

typedef void (^_FDViewControllerWillAppearInjectBlock)(UIViewController *viewController, BOOL animated);

看到这里也许你会提问,OC中不是不能给分类添加属性么?正常状况下,OC是不容许给OC添加属性的。可是利用Runtime的特性,这是能够办到的。实现方法以下:bash

- (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock { return objc_getAssociatedObject(self, _cmd);// 根据关联的key,获取关联的值。这里的key等于_cmd,_cmd等于fd_willAppearInjectBlock } - (void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block { // 第一个参数:给哪一个对象添加关联 // 第二个参数:关联的key,经过这个key获取 // 第三个参数:关联的value // 第四个参数:关联的策略 objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);//关联对象 }

动态给分类添加属性的方法是:less

objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

获取这个属性的方法是:函数

objc_getAssociatedObject(id object, const void *key)

还有一个方法是移除属性:atom

objc_removeAssociatedObjects(id object)

是的,这样就动态的给UIViewController的分类添加了fd_willAppearInjectBlock这么一个属性。spa

NOTE:在使用Runtime的这些方法的时候不要忘了导入objc/runtime.h这个头文件哦!3d

2.动态添加方法

要想动态添加方法咱们必须了解方法是如何执行的,一般咱们调用方法是经过[object message]这种方法,除了这种方法还有一种是比较少用的,就是[object performSelector:@selector(message)]这种方式。经过下面这张图咱们能够了解一下他们对消息的处理的不一样之处。指针


iOS消息转发.png

经过上图,咱们能够得知,要想动态添加方法必须是经过[object performSelector:@selector(message)]这种方式调用方法才能在运行时阶段经过Runtime的一些方法达到动态的添加方法。若是如今有一个Person类,在其它地方经过performSelector的方式调用Personrun方法。可是Person类中并无实现这个方法。code

Person p = [Person alloc] init];
// 这个时候即便Person类没有实现run方法编译器也不会报错 [p performSelector:@selector(run)];

这时候只须要在Person中实现resolveInstanceMethod:方法就能够达到动态添加方法的目的。

//首先咱们要在Person类里面实现咱们要动态添加的方法 // 要注意,默认方法都有两个隐式参数 void run(id self,SEL sel){ NSLog(@"%@ %@",self,NSStringFromSelector(sel)); } // 当一个对象调用未实现的方法,会调用这个方法处理,而且会把对应的方法列表传过来. // 恰好能够用来判断未实现的方法是否是咱们想要动态添加的方法 + (BOOL)resolveInstanceMethod:(SEL)sel{ //先判断一下传过来的是否是run方法 if (sel == @selector(run)){ //若是是run方法就动态添加run方法 class_addMethod(self.class, @selector(run),(IMP)run, "v@:"); // 第一个参数:给哪一个类添加方法 // 第二个参数:添加方法的方法编号 // 第三个参数:添加方法的函数实现(函数地址),若是是OC方法 //能够用+(IMP)instanceMethodForSelector:(SEL)aSelector;得到方法的实现。 // 第四个参数:方法的签名,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd } }

这样就达到了给一个类动态添加方法的效果了,若是想把方法转发给其余的类实现,须要处理消息转发的第二或第三个函数了。

3.替换系统自带的方法

当一些时候,系统自带效果知足不了咱们的时候,要么咱们自定义,要么直接替换系统的方法。在公有的API是没有方法办到的。咱们来看一段FDFullscreenPopGesture的代码(注释是我加的):

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class]; //获取系统方法的SEL SEL originalSelector = @selector(viewWillAppear:); //获取替换方法的SEL SEL swizzledSelector = @selector(fd_viewWillAppear:); //为了获取IMP指针,得到方法的Method Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); //为了安全起见,先判断是否已经存在要交换的方法 BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (success) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } - (void)fd_viewWillAppear:(BOOL)animated { //不要认为这句代码有错,其实很好理解,在调用这句的时候方法已经交换了 // Forward to primary implementation. [self fd_viewWillAppear:animated]; if (self.fd_willAppearInjectBlock) { self.fd_willAppearInjectBlock(self, animated); } }

经过上面的代码咱们能够看出来,替换系统自带的方式实现须要用到的重要方法是method_exchangeImplementations()方法,而且要注意替换方法里面对本身的调用。这个方法也就是人们常说的Method Swizzling黑魔法,用的时候要注意,这是一把双刃剑!

结尾

Runtime在项目中不多用,可是要理解它,理解了以后用起来也不危险。若是你喜欢个人文章,不妨扫一扫下面的二维码请我喝杯茶。祝你们在iOS开发的道路上玩得愉快!

相关文章
相关标签/搜索