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,却实现了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]

参考:
使用NSProxy和NSObject设计代理类的差异

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342