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,卻實現(xiàn)了NSObject的Protocol。相比于我們常見的各種繼承于NSObject的類,它自身的方法很少,不需要實例化init,也沒有kvc等各種亂七八糟的協(xié)議實現(xiàn)。但是它具有消息轉(zhuǎn)發(fā)的功能,即可以通過繼承它,重寫 -forwardInvocation: 和 -methodSignatureForSelector: 方法,來實現(xiàn)消息的轉(zhuǎn)發(fā)。正如它的名字,它是實現(xiàn)代理模式的利器。
常見的利用場景有兩種:
- 構(gòu)造代理類,實現(xiàn)對原始類中方法的hook
- 代理類避免循環(huán)引用,如nstimer、cadisplaylink
- 實現(xiàn)多重繼承,通過消息轉(zhuǎn)發(fā),將多個類的調(diào)用轉(zhuǎn)發(fā)到具體實現(xiàn)的類
可以參照網(wǎng)上的一些指導(dǎo)文章,不再贅述。這里著重講一個可能存在問題的需求場景:
問題描述
場景: 我們很多使用NSPorxy的場景,是通過繼承NSPorxy,來hook實現(xiàn)某些Protocol的delegate。然后通過Protocol描述的方法,來調(diào)用這個代理類proxyDelegate。
問題: 可能會存在一種需求,我們的proxyDelegate,實現(xiàn)了這個Protocol中的某些optional方法,而被代理的delegate類,并沒有實現(xiàn)。那么會發(fā)現(xiàn),proxyDelegate中對這些optional方法的實現(xiàn),無法被調(diào)用。
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是實現(xiàn)BProtocol的delegate類,我們用代理類AProxy實現(xiàn)對BTarget的hook。然后是調(diào)用代碼(這里為了簡單,只模擬對delegate的調(diào)用):
BTarget *aDelegate = [BTarget new];
AProxy *proxyDelegate = [AProxy proxyWithTarget:aDelegate];
if ([proxyDelegate respondsToSelector:@selector(showName)]) {
[proxyDelegate showName];
}
if ([proxyDelegate respondsToSelector:@selector(show)]) {
[proxyDelegate show];
}
執(zhí)行的結(jié)果:
-[AProxy showName]
-[BTarget showName]
showName正常調(diào)用到,而show沒有被調(diào)用。
原因&結(jié)論:
原因很簡單,NSProxy的respondsToSelector:返回了NO!雖然proxyDelegate中的確有show方法的實現(xiàn)。
實際上,考察我們對NSObject類常用的respondsToSelector:和isKindOfClass:兩個方法,NSProxy的處理方式跟NSObject是不同的。
即便我們在AProxy中加入respondsToSelector:
//... in AProxy
- (BOOL)respondsToSelector:(SEL)aSelector {
return [super respondsToSelector:aSelector];
}
//...
然后在methodSignatureForSelector:打斷點:po selector,我們會發(fā)現(xiàn)會打印兩次respondsToSelector:,也就是說,respondsToSelector:這個方法消息被轉(zhuǎn)發(fā)了!另外一點,[super respondsToSelector:aSelector]兩次調(diào)用(showName和show)都會返回NO!
對繼承于NSObject的類來說,respondsToSelector:的調(diào)用不會轉(zhuǎn)發(fā),也會正確返回是否含有selector,而不會直接返回NO然后走轉(zhuǎn)發(fā)。
解決
一種解決方案,是在respondsToSelector:加入method白名單,即特定實現(xiàn)的method的SEL,返回YES:
- (BOOL)respondsToSelector:(SEL)aSelector {
if (aSelector == @selector(show)) {
return YES;
}
return [super respondsToSelector:aSelector];
}
添加以上代碼,執(zhí)行答應(yīng)出結(jié)果:
-[AProxy showName]
-[BTarget showName]
-[AProxy show]