专注iOS开发(OC/Swift)Runtime源码

NSProxy

2018-07-19  本文已影响67人  洲洲哥

吾尝以为NSObject是Apple的所有Objective-C引用类型的老祖宗,直到后来知道了NSProxy的存在…

NSProxyclass NSObject平级,彼此没有继承关系;唯一的相同点是它们都遵循protocol NSObject。尚未在开发中使用过NSProxy,最近琢磨需求开发的方案时注意到了这个类的存在,有不少疑问,譬如NSproxy存在的意义是什么?如何使用它呢?本文将疑惑记录下来,并尝试自我解惑。

NSProxy简介

NSProxy是一个抽象类,它实现了protocol NSObject所要求的基本方法,譬如内省相关的-isKindOfClass:、派发消息相关的performSelector系列方法等;但是不能直接使用它创建对象。

子类化NSProxy的要求也很简单,实现-forwardInvocation:-methodSignatureForSelector:这两个方法即可;显然,这两个方法与消息转发相关,实现这两个方法的典型姿势是:

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    [anInvocation invokeWithTarget:_realObject];  // _realObject是自定义的实例变量
}
– (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [_realObject methodSignatureForSelector:aSelector];
}

NSProxy存在的意义是为它人做嫁衣,Apple的说法是:

Typically, a message to a proxy is forwarded to the real object or causes the proxy to load (or transform itself into) the real object.

NSProxy的消息转发机制

虽然NSProxyclass NSObject都定义了-forwardInvocation:-methodSignatureForSelector:,但这两个方法并没有在protocol NSObject中声明;两者对这俩方法的调用逻辑更是完全不同。

对于class NSObject而言,接收到消息后先去自身的方法列表里找匹配的selector,如果找不到,会沿着继承体系去superclass的方法列表找;如果还找不到,先后会经过+resolveInstanceMethod:-forwardingTargetForSelector:处理,处理失败后,才会到-methodSignatureForSelector:/-forwardInvocation:进行最后的挣扎。更详细的叙述,详见NSObject的消息转发机制

但对于NSProxy,接收unknown selector后,直接回调-methodSignatureForSelector:/-forwardInvocation:,消息转发过程比class NSObject要简单得多。

相对于class NSObjectNSProxy的另外一个非常重要的不同点也值得注意:NSProxy会将自省相关的selector直接forward到-forwardInvocation:回调中,这些自省方法包括:

- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;

简单来说,这4个selector的实际接收者realObject,而不是NSProxy对象本身。但另一方面,NSProxy并没有将performSelector系列selector也forward到-forwardInvocation:,换句话说,[proxy performSelector:someSelector]的真正处理者仍然是proxy自身,只是后续会将someSelector给forward到-forwardInvocation:回调,然后经由realObject处理。

P.S: 如上这个说法我并没有找到比较权威的官方直接说明,只是写demo验证了自己的判断。

P.P.S: NSProxy自省方法的默认实现是将消息forward到realObject,如果不想这样,该怎么办?简单,override相关方法即可。只不过override时不要super invoke。

-init和-isProxy

除了上述的-methodSignatureForSelector:-forwardInvocation:NSProxy另外两个方法也非常值得说一说。

首先是-init。事实上,NSProxy没有定义这个方法,对于直接继承NSProxy的类,创建对象时不需要使用[super init]之类的调用进行初始化。为啥Apple不为NSProxy提供默认的构造器呢?我想这和它的定位有关吧,它是一个抽象类,不提供默认的-init反而能进一步阻止用户直接使用NSProxy创建对象。

P.S: 从class NSObject开源代码来看,class NSObject-init其实也没干啥事儿。

另一个值得一提的方法是-(BOOL)isProxy,用户可以根据该方法的返回值判断对象是否继承自NSObject

P.S: 看起来这个方法很重要,但尚未搞清楚它的内涵意义,以后再补充吧!

使用NSProxy设计代理类

使用NSProxy设计简单的代理类非常合适。老司机老谭在其博客使用NSProxy和NSObject设计代理类的差异中探讨过使用NSProxy设计代理类的优势,简单罗列如下:

或许是我看到的东西太少,NSProxy的使用场景并不多,只是用于设计一些简单的代理,譬如常见的应用场景是解决NSTimer与其target之间的循环引用问题(参考这里)。复杂的代理逻辑,譬如ReactiveCocoa的RACDelegateProxy,大多基于NSObject实现,因为NSObject要强大得多,譬如支持KVO、runtime,而上述罗列的class NSObject的不足,只要基本功扎实,心思缜密,理论上是可以绕过的,只不过要写大量的override逻辑。另外,我认为另一个重要的原因是class NSObject的相关实现是开源的,逻辑比较可控。而NSProxy属于NSFoundation的一部分,并没有开源。

另外,著名的第三方库libextobjc基于NSProxy设计了一个非常有意思的类:EXTNil。该类基本上实现了神奇的nil指针功能:可以接收任何消息而不抛出doesNotRecognizeSelector:异常,但不进行任何处理。之所以说「基本上」,是因为还不彻底,向EXTNil发送的有效消息必须在工程全局范围内能找到有效selector,否则仍然会抛出doesNotRecognizeSelector:异常

有些地方说的不到位,还请各位看官指正。。。

可以来微信公众号(洲洲哥)后台给我们留言。 快来扫码关注我们吧!

公众号二维码
上一篇下一篇

猜你喜欢

热点阅读