吾尝以为NSObject
是Apple的所有Objective-C引用类型的老祖宗,直到后来知道了NSProxy
的存在…
NSProxy
和class 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的消息转发机制
虽然NSProxy
和class NSObject
都定义了-forwardInvocation:
和-methodSignatureForSelector:
,但这两个方法并没有在protocol NSObject
中声明;两者对这俩方法的调用逻辑更是完全不同。
对于class NSObject
而言,接收到消息后先去自身的方法列表里找匹配的selector,如果找不到,会沿着继承体系去superclass的方法列表找;如果还找不到,先后会经过+resolveInstanceMethod:
和-forwardingTargetForSelector:
处理,处理失败后,才会到-methodSignatureForSelector:
/-forwardInvocation:
进行最后的挣扎。更详细的叙述,详见NSObject的消息转发机制。
但对于NSProxy
,接收unknown selector后,直接回调-methodSignatureForSelector:
/-forwardInvocation:
,消息转发过程比class NSObject
要简单得多。
相对于class NSObject
,NSProxy
的另外一个非常重要的不同点也值得注意: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
会将自省相关方法直接forward到forwardInvocation:
回调,如果基于class NSObject
实现类似的效果,得另外写不少代码,比较麻烦。 -
NSProxy
比class NSObject
干净得多,后者各种category(譬如NSObject(NSKeyValueCoding)
)定义了诸多方法,这些方法的存在使得消息转发变得更麻烦,因为得区分class NSObject
对象本身和其所指向的realObject。
或许是我看到的东西太少,NSProxy
的使用场景并不多,只是用于设计一些简单的代理,譬如常见的应用场景是解决NSTimer
与其target之间的循环引用问题(参考这里)。复杂的代理逻辑,譬如ReactiveCocoa的RACDelegateProxy
,大多基于NSObject
实现,因为NSObject
要强大得多,譬如支持KVO、runtime,而上述罗列的class NSObject
的不足,只要基本功扎实,心思缜密,理论上是可以绕过的,只不过要写大量的override逻辑。另外,我认为另一个重要的原因是class NSObject
的相关实现是开源的,逻辑比较可控。而NSProxy
属于NSFoundation的一部分,并没有开源。
另外,著名的第三方库libextobjc基于NSProxy
设计了一个非常有意思的类:EXTNil
。该类基本上实现了神奇的nil
指针功能:可以接收任何消息而不抛出doesNotRecognizeSelector:
异常,但不进行任何处理。之所以说「基本上」,是因为还不彻底,向EXTNil
发送的有效消息必须在工程全局范围内能找到有效selector,否则仍然会抛出doesNotRecognizeSelector:
异常
有些地方说的不到位,还请各位看官指正。。。
- 如有问题可添加QQ群:234812704
- 欢迎各位一块学习,提高逼格!
- 也可以添加洲洲哥的微信公众号
可以来微信公众号(洲洲哥)后台给我们留言。 快来扫码关注我们吧!
公众号二维码
网友评论