NSProxy

NSProxy

前两天被问到有没有接触过NSHashTable and NSMapTable,由于项目中没用到过,所以之前也没有去了解过。然后,就在网上查了一下,结果在一篇文章中看到了一段话如下:

如果一个开发者想要存储一个weak类型的值或者使用一个没有实现NSCopying协议的object作为NSDictionary的key,他可能会很聪明的想到NSValue +valueWithNonretainedObject。

于是乎又去看看了valueWithNonretainedObject相关的内容,然后在这一篇文章中看到了本文的主角NSProxy

偷窥NSProxy

NSProxy是和NSObject同级的一个类,可以说他是一个虚拟类,它只是实现了NSObject的协议。它的作用有点类似于一个复制的类。
我们可以看一下NSProxy里包含哪些内容:
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实现更多你想实现的效果。本文代码地址请戳这里。想了解更多的内容你可以参考下面的几篇文章:

  1. https://www.jianshu.com/p/a7187e014c03
  2. https://www.jianshu.com/p/923f119333d8
  3. https://blog.csdn.net/ssirreplaceable/article/details/53375972

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2020 KNOWLEDGE IS POWER All Rights Reserved.

访客数 : | 访问量 :