NSProxy概述
An abstract superclass defining an API for objects that act as stand-ins for other objects or for objects that don’t exist yet.
正如Apple对NSPorxy的描述,NSPorxy是一个虚类。它不继承于NSObject,却实现了NSObject的Protocol。相比于我们常见的各种继承于NSObject的类,它自身的方法很少,不需要实例化init,也没有kvc等各种乱七八糟的协议实现。但是它具有消息转发的功能,即可以通过继承它,重写 -forwardInvocation: 和 -methodSignatureForSelector: 方法,来实现消息的转发。正如它的名字,它是实现代理模式的利器。
常见的利用场景有两种:
- 构造代理类,实现对原始类中方法的hook
- 代理类避免循环引用,如nstimer、cadisplaylink
- 实现多重继承,通过消息转发,将多个类的调用转发到具体实现的类
可以参照网上的一些指导文章,不再赘述。这里着重讲一个可能存在问题的需求场景:
问题描述
场景: 我们很多使用NSPorxy的场景,是通过继承NSPorxy,来hook实现某些Protocol的delegate。然后通过Protocol描述的方法,来调用这个代理类proxyDelegate。
问题: 可能会存在一种需求,我们的proxyDelegate,实现了这个Protocol中的某些optional方法,而被代理的delegate类,并没有实现。那么会发现,proxyDelegate中对这些optional方法的实现,无法被调用。
show me the code :
// delegate protocol
@protocol BProtocol <NSObject>
@required
- (void)showName;
@optional
- (void)show;
@end
// a delegate implement protocol
@interface BTarget : NSObject <BProtocol>
- (void)showName;
@end
@implementation BTarget
- (void)showName {
NSLog(@"%s", __func__);
}
@end
// a proxy class
@interface AProxy : NSProxy <BProtocol>
@property (nonatomic, weak, readonly, nullable) id target;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id __nullable)target;
@end
@implementation AProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[self alloc] initWithTarget:target];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [self.target methodSignatureForSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
- (void)showName {
NSLog(@"%s", __func__);
if ([self.target respondsToSelector:@selector(showName)]) {
[self.target showName];
}
}
- (void)show {
NSLog(@"%s", __func__);
if ([self.target respondsToSelector:@selector(show)]) {
[self.target showName];
}
}
@end
如上代码,BTarget是实现BProtocol的delegate类,我们用代理类AProxy实现对BTarget的hook。然后是调用代码(这里为了简单,只模拟对delegate的调用):
BTarget *aDelegate = [BTarget new];
AProxy *proxyDelegate = [AProxy proxyWithTarget:aDelegate];
if ([proxyDelegate respondsToSelector:@selector(showName)]) {
[proxyDelegate showName];
}
if ([proxyDelegate respondsToSelector:@selector(show)]) {
[proxyDelegate show];
}
执行的结果:
-[AProxy showName]
-[BTarget showName]
showName正常调用到,而show没有被调用。
原因&结论:
原因很简单,NSProxy的respondsToSelector:返回了NO!虽然proxyDelegate中的确有show方法的实现。
实际上,考察我们对NSObject类常用的respondsToSelector:和isKindOfClass:两个方法,NSProxy的处理方式跟NSObject是不同的。
即便我们在AProxy中加入respondsToSelector:
//... in AProxy
- (BOOL)respondsToSelector:(SEL)aSelector {
return [super respondsToSelector:aSelector];
}
//...
然后在methodSignatureForSelector:打断点:po selector,我们会发现会打印两次respondsToSelector:,也就是说,respondsToSelector:这个方法消息被转发了!另外一点,[super respondsToSelector:aSelector]两次调用(showName和show)都会返回NO!
对继承于NSObject的类来说,respondsToSelector:的调用不会转发,也会正确返回是否含有selector,而不会直接返回NO然后走转发。
解决
一种解决方案,是在respondsToSelector:加入method白名单,即特定实现的method的SEL,返回YES:
- (BOOL)respondsToSelector:(SEL)aSelector {
if (aSelector == @selector(show)) {
return YES;
}
return [super respondsToSelector:aSelector];
}
添加以上代码,执行答应出结果:
-[AProxy showName]
-[BTarget showName]
-[AProxy show]