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)。但是它具有消息轉發(fā)的功能陌僵,即可以通過繼承它轴合,重寫 -forwardInvocation: 和 -methodSignatureForSelector: 方法,來實現(xiàn)消息的轉發(fā)碗短。正如它的名字受葛,它是實現(xiàn)代理模式的利器。
常見的利用場景有兩種:
- 構造代理類豪椿,實現(xiàn)對原始類中方法的hook
- 代理類避免循環(huán)引用奔坟,如nstimer、cadisplaylink
- 實現(xiàn)多重繼承搭盾,通過消息轉發(fā)咳秉,將多個類的調用轉發(fā)到具體實現(xiàn)的類
可以參照網(wǎng)上的一些指導文章,不再贅述鸯隅。這里著重講一個可能存在問題的需求場景:
問題描述
場景: 我們很多使用NSPorxy的場景澜建,是通過繼承NSPorxy,來hook實現(xiàn)某些Protocol的delegate蝌以。然后通過Protocol描述的方法炕舵,來調用這個代理類proxyDelegate。
問題: 可能會存在一種需求跟畅,我們的proxyDelegate咽筋,實現(xiàn)了這個Protocol中的某些optional方法,而被代理的delegate類徊件,并沒有實現(xiàn)奸攻。那么會發(fā)現(xiàn),proxyDelegate中對這些optional方法的實現(xiàn)虱痕,無法被調用睹耐。
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硝训。然后是調用代碼(這里為了簡單,只模擬對delegate的調用):
BTarget *aDelegate = [BTarget new];
AProxy *proxyDelegate = [AProxy proxyWithTarget:aDelegate];
if ([proxyDelegate respondsToSelector:@selector(showName)]) {
[proxyDelegate showName];
}
if ([proxyDelegate respondsToSelector:@selector(show)]) {
[proxyDelegate show];
}
執(zhí)行的結果:
-[AProxy showName]
-[BTarget showName]
showName正常調用到新思,而show沒有被調用窖梁。
原因&結論:
原因很簡單,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:這個方法消息被轉發(fā)了山析!另外一點,[super respondsToSelector:aSelector]兩次調用(showName和show)都會返回NO掏父!
對繼承于NSObject的類來說笋轨,respondsToSelector:的調用不會轉發(fā),也會正確返回是否含有selector赊淑,而不會直接返回NO然后走轉發(fā)爵政。
解決
一種解決方案,是在respondsToSelector:加入method白名單陶缺,即特定實現(xiàn)的method的SEL钾挟,返回YES:
- (BOOL)respondsToSelector:(SEL)aSelector {
if (aSelector == @selector(show)) {
return YES;
}
return [super respondsToSelector:aSelector];
}
添加以上代碼,執(zhí)行答應出結果:
-[AProxy showName]
-[BTarget showName]
-[AProxy show]