/**此的方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。 */+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);/**此方法和上面的方法一样,只是处理的是对象方法。 */+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);/**此方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。 */- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);/**此方法是将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。 */- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");二、方法拦截添加运用
首先我们创建一个测试类TestClass,然后后调用一个该类里面没有的对象方法_testClass = [TestClass new]; [_testClass performSelector:@selector(testMethod:) withObject:@"test"]; 正常情况下程序会崩溃,因为该类没有此方法,父类也没有,我们也没有做相应处理,日志如下2017-02-07 10:53:42.318 RuntimeDemo[1809:61956] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TestClass testMethod:]: unrecognized selector sent to instance 0x6080000021d0' 如果我们重写resolveInstanceMethod并在方法里面增加该方法,那面程序就会运行通过,void runAddMethod(id self, SEL _cmd, NSString *string){ NSLog(@"add C IMP %@", string);}+ (BOOL)resolveInstanceMethod:(SEL)sel{ //给本类动态添加一个方法 if ([NSStringFromSelector(sel) isEqualToString:@"testMethod:"]) { class_addMethod(self, sel, (IMP)runAddMethod, "v@:*"); } return YES;}其中class_addMethod的四个参数分别是:
Class cls 给哪个类添加方法,本例中是selfSEL name 添加的方法,本例中是重写的拦截调用传进来的selector。IMP imp 方法的实现,C方法的方法实现可以直接获得。如果是OC方法,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;获得方法的实现。"v@:*"方法的签名,代表有一个参数的方法。
三、关联对象
假如正在使用一个系统的类并不能满足你的需求,你需要额外添加一个属性。这种情况的一般解决办法就是继承。但是,只增加一个属性,就去继承一个类,总是觉得太麻烦类。这个时候,runtime的关联属性就发挥它的作用了。//首先定义一个全局变量,用它的地址作为关联对象的keystatic char associatedObjectKey;//设置关联对象objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串属性", OBJC_ASSOCIATION_RETAIN_NONATOMIC); //获取关联对象NSString *string = objc_getAssociatedObject(target, &associatedObjectKey);NSLog(@"AssociatedObject = %@", string);objc_setAssociatedObject的四个参数:id object给谁设置关联对象。const void *key关联对象唯一的key,获取时会用到。id value关联对象。objc_AssociationPolicy关联策略,有以下几种策略:enum { OBJC_ASSOCIATION_ASSIGN = 0, OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403};如果你熟悉OC,看名字应该知道这几种策略就是属性修饰关键字。objc_getAssociatedObject的两个参数。id object获取谁的关联对象。const void *key根据这个唯一的key获取关联对象。其实,你还可以把添加和获取关联对象的方法写在你需要用到这个功能的类的类别中,方便使用。//添加关联对象- (void)addAssociatedObject:(id)object{ objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}//获取关联对象- (id)getAssociatedObject{ return objc_getAssociatedObject(self, _cmd);}注意:这里面我们把getAssociatedObject方法的地址作为唯一的key,_cmd代表当前调用方法的地址。三、方法交换
方法交换,顾名思义,就是将两个方法的实现交换。例如,将A方法和B方法交换,调用A方法的时候,就会执行B方法中的代码,反之亦然。#import "UIViewController+swizzling.h"#import <objc/runtime.h>@implementation UIViewController (swizzling)//load方法会在类第一次加载的时候被调用//调用的时间比较靠前,适合在这个方法里做方法交换+ (void)load{ //方法交换应该被保证,在程序中只会执行一次 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //获得viewController的生命周期方法的selector SEL systemSel = @selector(viewWillAppear:); //自己实现的将要被交换的方法的selector SEL swizzSel = @selector(swiz_viewWillAppear:); //两个方法的Method Method systemMethod = class_getInstanceMethod([self class], systemSel); Method swizzMethod = class_getInstanceMethod([self class], swizzSel); //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败 BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod)); if (isAdd) { //如果成功,说明类中不存在这个方法的实现 //将被交换方法的实现替换到这个并不存在的实现 class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod)); }else{ //否则,交换两个方法的实现 method_exchangeImplementations(systemMethod, swizzMethod); } });}- (void)swiz_viewWillAppear:(BOOL)animated{ //这时候调用自己,看起来像是死循环 //但是其实自己的实现已经被替换了 [self swiz_viewWillAppear:animated]; NSLog(@"swizzle");}@end在一个自己定义的viewController中重写viewWillAppear- (void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; NSLog(@"viewWillAppear");}
新闻热点
疑难解答