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)。但是它具有消息轉發(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]

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

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饱岸,一起剝皮案震驚了整個濱河市掺出,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苫费,老刑警劉巖汤锨,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異百框,居然都是意外死亡闲礼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門铐维,熙熙樓的掌柜王于貴愁眉苦臉地迎上來位仁,“玉大人,你說我怎么就攤上這事方椎。” “怎么了钧嘶?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵棠众,是天一觀的道長。 經(jīng)常有香客問我有决,道長闸拿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任书幕,我火速辦了婚禮新荤,結果婚禮上,老公的妹妹穿的比我還像新娘台汇。我一直安慰自己苛骨,他們只是感情好篱瞎,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著痒芝,像睡著了一般俐筋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上严衬,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天澄者,我揣著相機與錄音,去河邊找鬼请琳。 笑死粱挡,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的俄精。 我是一名探鬼主播询筏,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嘀倒!你這毒婦竟也來了屈留?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤测蘑,失蹤者是張志新(化名)和其女友劉穎灌危,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碳胳,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡勇蝙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了挨约。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片味混。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖诫惭,靈堂內(nèi)的尸體忽然破棺而出翁锡,到底是詐尸還是另有隱情,我是刑警寧澤夕土,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布馆衔,位于F島的核電站,受9級特大地震影響怨绣,放射性物質發(fā)生泄漏角溃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一篮撑、第九天 我趴在偏房一處隱蔽的房頂上張望减细。 院中可真熱鬧,春花似錦赢笨、人聲如沸未蝌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽树埠。三九已至糠馆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間怎憋,已是汗流浹背又碌。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绊袋,地道東北人毕匀。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像癌别,于是被迫代替她去往敵國和親皂岔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353