详解NSProxy
一、概述
NSProxy是少有的没有从NSObject类
继承的类,它实现了NSObject协议
。其日常使用不多,但是也是有一些比较好的使用场景。
// 实现了NSObject协议
@interface NSProxy <NSObject> {
__ptrauth_objc_isa_pointer Class isa;
}
+ (id)alloc;
+ (id)allocWithZone:(nullable NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE;
+ (Class)class;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
- (void)dealloc;
- (void)finalize;
@property (readonly, copy) NSString *description;
@property (readonly, copy) NSString *debugDescription;
+ (BOOL)respondsToSelector:(SEL)aSelector;
- (BOOL)allowsWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);
- (BOOL)retainWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);
// - (id)forwardingTargetForSelector:(SEL)aSelector;
@end
二、基本作用
正如该类的名字所示的那样,这个类最基本的作用就是作为一个代理。通过持有其他对象并自动的把消息转发给所持有(代理)的对象类来实现代理的功能。这么说起来,都有被代理对象了,为什么要代理?其实现实世界是负责的,比如代理对象的创建代价比较昂贵。另外,在这个基本作用的基础之上可以构建更加强大的应用(本文第三小节会讲到)。
/*------------头文件-------------*/
@interface MyProxy : NSProxy
+ (instancetype)proxyWithTarget:(NSObject *)target;
@end
/*------------实现文件-------------*/
@interface MyProxy ()
@property (nonatomic, strong) NSObject *target;
@end
@implementation MyProxy
// NSProxy 没有初始化方法,需要我们自己实现
- (instancetype)initWithTarget:(NSObject *)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(NSObject *)target {
MyProxy *proxy = [[MyProxy alloc] initWithTarget:target];
return proxy;
}
// 获得调用方法的方法签名,必须实现的方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
NSMethodSignature *signature;
if (_target) {
signature = [_target methodSignatureForSelector:sel];
} else {
signature= [super methodSignatureForSelector:sel];
}
return signature;
}
// 为调用设置目标,必须实现的方法
- (void)forwardInvocation:(NSInvocation *)invocation {
if (!_target) {
return;
}
[invocation invokeWithTarget:_target];
}
@end
/*------------测试代码-------------*/
NSString *aStr = (NSString *)[MyProxy proxyWithTarget:@"here"];
[aStr length];
// 看起来测试代码平淡无奇,但是需要注意的是:aStr能够响应不属于自己的消息
// aStr 虽然进行了类型转换,但是它实质上是MyProxy类型
三、实际应用
3.1 解除循环引用
我们都知道NSTimer引起循环引用的场景,以VC含有Timer,Timer持有VC为例,循环引用如下图所示。
timer的循环引用
解决此问题的方法也很简单,使用如下代码。
/*------------头文件-------------*/
@interface MyProxy : NSProxy
+ (instancetype)proxyWithTarget:(NSObject *)target;
@end
/*------------实现文件-------------*/
@interface MyProxy ()
@property (nonatomic, weak) NSObject *target;
@end
@implementation MyProxy
// NSProxy 没有初始化方法,需要我们自己实现
- (instancetype)initWithTarget:(NSObject *)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(NSObject *)target {
MyProxy *proxy = [[MyProxy alloc] initWithTarget:target];
return proxy;
}
// 获得调用方法的方法签名,必须实现的方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
NSMethodSignature *signature;
if (_target) {
signature = [_target methodSignatureForSelector:sel];
} else {
signature= [super methodSignatureForSelector:sel];
}
return signature;
}
// 为调用设置目标,必须实现的方法
- (void)forwardInvocation:(NSInvocation *)invocation {
if (!_target) {
return;
}
[invocation invokeWithTarget:_target];
}
@end
/*------------实现文件-------------*/
// 创建timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[MyProxy proxyWithTarget:self] selector:@selector(timerEvent) userInfo:nil repeats:YES];
// 销毁timer
-(void)dealloc {
[_timer invalidate];
}
源码之前了无秘密!
其解除循环依赖本质上依赖的是weak,但是由于其具有消息转发作用,所以对原有代码逻辑改动很小!
3.2 模拟多继承
模拟多继承也是很简单的,代码如下:
ClassA *clsA = [[ClassA alloc]init];
ClassB *clsB = [[ClassB alloc]init];
MyProxy *proxy = [SFProxy alloc];
// 变身为clsA的代理
[proxy transformToObject:clsA];
[proxy performSelector:@selector(funcA) withObject:nil];
// 变身为clsB的代理
[proxy transformToObject:clsB];
[proxy performSelector:@selector(funcB) withObject:nil];
四、与NSObject的区别
虽然NSProxy和class NSObject都定义了-forwardInvocation:和-methodSignatureForSelector:,但这两个方法并没有在protocol NSObject中声明;两者对这俩方法的调用逻辑更是完全不同。
对于class NSObject而言,接收到消息后先去自身的方法列表里找匹配的selector,如果找不到,会沿着继承体系去superclass的方法列表找;如果还找不到,先后会经过+resolveInstanceMethod:和-forwardingTargetForSelector:处理,处理失败后,才会到-methodSignatureForSelector:/-forwardInvocation:进行最后的挣扎.
但对于NSProxy,接收unknown selector后,直接回调-methodSignatureForSelector:/-forwardInvocation:,消息转发过程比class NSObject要简单得多。
相对于class NSObject,NSProxy的另外一个非常重要的不同点也值得注意:NSProxy会将自省相关的selector直接forward到-forwardInvocation:回调中,这些自省方法包括:
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;
这4个selector的实际接收者realObject,而不是NSProxy对象本身。但另一方面,NSProxy并没有将performSelector系列selector也forward到-forwardInvocation:,换句话说,[proxy performSelector:someSelector]的真正处理者仍然是proxy自身,只是后续会将someSelector给forward到-forwardInvocation:回调,然后经由realObject处理。
参考如下文章:
1、https://juejin.cn/post/6847902216209039367
2、https://juejin.cn/post/6844903922008604686