NSProxy 與 respondsToSelector:

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]

參考:
使用NSProxy和NSObject設(shè)計代理類的差異

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容