NSProxy
前两天被问到有没有接触过NSHashTable and NSMapTable,由于项目中没用到过,所以之前也没有去了解过。然后,就在网上查了一下,结果在一篇文章中看到了一段话如下:
如果一个开发者想要存储一个weak类型的值或者使用一个没有实现NSCopying协议的object作为NSDictionary的key,他可能会很聪明的想到NSValue +valueWithNonretainedObject。
于是乎又去看看了valueWithNonretainedObject相关的内容,然后在这一篇文章中看到了本文的主角NSProxy。
偷窥NSProxy
NSProxy是和NSObject同级的一个类,可以说他是一个虚拟类,它只是实现了NSObject的协议。它的作用有点类似于一个复制的类。
我们可以看一下NSProxy里包含哪些内容:
可以看得出来,它确实遵守了NSObject协议,而且第一个Ivr是一个isa指针,所以我们可以把它当作一个NSObject或者派生类来使用。
通过代码加深偷窥
我们可以通过代码来看看,我么可以做哪些操作,可以用来做些什么。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| // XZFProxy.h #import <Foundation/Foundation.h>
@interface XZFProxy : NSProxy @property (nonatomic, copy) NSString *className; //类名,用来过滤类 - (void)initWithFromObjc:(NSObject *)objc; //初始化方法 @end
// XZFProxy.m #import "XZFProxy.h"
@interface XZFProxy() @property (nonatomic, strong) NSObject *objc; @end
@implementation XZFProxy
+ (id)proxyWithObjc:(NSObject *)objc { XZFProxy *xzfProxy = [XZFProxy alloc]; xzfProxy.objc = objc; return xzfProxy; } /** 初始化
@param objc 传入一个objc */ - (void)changeObjc:(NSObject *)objc { self.objc = objc; }
#pragma mark - 需要实现的方法 ///1.查询该方法的方法签名,用来生成 NSInvocation - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { NSMethodSignature * mSignature = nil; if ([self.objc methodSignatureForSelector:sel]) { mSignature = [self.objc methodSignatureForSelector:sel]; } else { mSignature = [super methodSignatureForSelector:sel]; } return mSignature; }
///2.有了方法签名,调用方法实现 -(void)forwardInvocation:(NSInvocation *)invocation { //在这里我们可以实现对任意对象的行为进行拦截 if (self.objc) { //拦截方法的执行者为复制的对象 [invocation setTarget:self.objc]; if ([self.objc isKindOfClass:[NSClassFromString(self.className) class]]) { //拦截参数 argument:表示的是方法的参数 index:表示的是方法参数的下标 NSString *str = @"不爱学习,只想玩。。。"; [invocation setArgument:&str atIndex:3]; } //开始调用方法 [invocation invoke]; } } @end
|
代码的调用:
1 2 3 4 5 6 7 8 9 10 11 12
| - (void)viewDidLoad { [super viewDidLoad]; XZFTeacher *teacher = [[XZFTeacher alloc] init]; XZFProxy *xzfProxy = [XZFProxy proxyWithObjc:teacher]; xzfProxy.className = @"XZFTeacher"; [xzfProxy performSelector:@selector(callStudentWithName:toLearnProject:) withObject:@"张三" withObject:@"英语"]; XZFStudent *student = [[XZFStudent alloc] initWithName:@"李四"]; [xzfProxy changeObjc:student]; [xzfProxy performSelector:@selector(startLearnProject:) withObject:@"数学"]; }
|
我们可以在控制台查看打印的结果:
我们很轻松的改变了Teacher的执行内容。
当然我们还可以通过下面两个方法,处理一个方法的返回值:
1 2
| - (void)getReturnValue:(void *)retLoc; - (void)setReturnValue:(void *)retLoc;
|
我们可以用下面的代码来获取被代理对象的方法返回值:
1 2
| NSString *str; [invocation setReturnValue:&str];
|
通过下面的代码来修改被代理对象的方法的返回值:
1 2
| NSString *str = @"迎娶白富美,出任CEO"; [invocation setReturnValue:&str];
|
值得注意的是:上述方法是拷贝指针所指向的数据,所以要传递str指针的指针,这样才能把str设置为返回值的地址,切记不要弄混淆了。
总结
通过NSProxy,不仅可以修改方法的执行结果,还可以实现埋点计数等功能。你可以开动你的大脑去想想怎么利用NSProxy实现更多你想实现的效果。本文代码地址请戳这里。想了解更多的内容你可以参考下面的几篇文章:
- https://www.jianshu.com/p/a7187e014c03
- https://www.jianshu.com/p/923f119333d8
- https://blog.csdn.net/ssirreplaceable/article/details/53375972