在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
要想动态添加方法咱们必须了解方法是如何执行的,一般咱们调用方法是经过[object message]
这种方法,除了这种方法还有一种是比较少用的,就是[object performSelector:@selector(message)]
这种方式。经过下面这张图咱们能够了解一下他们对消息的处理的不一样之处。指针
经过上图,咱们能够得知,要想动态添加方法必须是经过[object performSelector:@selector(message)]
这种方式调用方法才能在运行时阶段经过Runtime的一些方法达到动态的添加方法。若是如今有一个Person
类,在其它地方经过performSelector
的方式调用Person
的run
方法。可是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 } }
这样就达到了给一个类动态添加方法的效果了,若是想把方法转发给其余的类实现,须要处理消息转发的第二或第三个函数了。
当一些时候,系统自带效果知足不了咱们的时候,要么咱们自定义,要么直接替换系统的方法。在公有的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开发的道路上玩得愉快!