參考資料:
https://my.oschina.net/iq19900204/blog/411450
http://blog.csdn.net/devday/article/details/7418022
1. NSProxy和NSObject
基本所有的iOS中的類都是NSObject的字類,但是NSProxy不是。
NSProxy是一個(gè)虛類,你可以通過繼承它,并重載下面兩個(gè)方法以實(shí)現(xiàn)將消息轉(zhuǎn)發(fā)到另一個(gè)實(shí)體。
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
這里最好描述一下虛類的概念:虛類又叫做抽象類,這個(gè)類主要定義一些方法然后讓子類去實(shí)現(xiàn)。正如有人描述的,動(dòng)物是一個(gè)大概念,但是通常情況下你不會(huì)去定義動(dòng)物的對(duì)象;而是先產(chǎn)生繼承的字類貓啊,狗啊的,再去實(shí)例化。這個(gè)動(dòng)物就可以用虛類來表示了,畢竟動(dòng)物是有共性的。以上純屬個(gè)人理解,非官方語言描述!
NSObject既是對(duì)象的基類,又是一種協(xié)議。它的頭文件是這樣的:
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
而NSObject協(xié)議的定義是這樣的:
#include <objc/objc.h>
#include <objc/NSObjCRuntime.h>
@class NSString, NSMethodSignature, NSInvocation;
@protocol NSObject
- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;
@property (readonly) Class superclass;
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'anObject.dynamicType' instead");
- (instancetype)self;
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
- (BOOL)isProxy;
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;
- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
@property (readonly, copy) NSString *description;
@optional
@property (readonly, copy) NSString *debugDescription;
@end
很全很熟悉不是嗎?!
NSProxy一個(gè)虛類,但是同時(shí)它也實(shí)現(xiàn)了NSObject協(xié)議,它的定義是這樣的:
@interface NSProxy <NSObject> {
Class isa;
}
所以NSProxy的字類可以實(shí)現(xiàn)NSObject協(xié)議中的一些方法。
2. 方法重定向
比較優(yōu)秀的iOS工程師應(yīng)該都知道對(duì)象在調(diào)用方法時(shí)的機(jī)制,會(huì)尋找當(dāng)前類的方法緩存,方法鏈表;然后依次向父類去尋找,一直到NSObject,到了NSobject還是找不到的話,我們有機(jī)會(huì)使用上面的方法來轉(zhuǎn)移方法的注意力了。
到這里我本來想引用另一個(gè)博客中定義了NSProxy父類的方法,但是博主不允許隨便轉(zhuǎn)載,大家有空自己去看吧。
http://blog.csdn.net/devday/article/details/7418022
我們先定義一個(gè)TestTool類。
//TestTool.h
@interface TestTool : NSObject
{
id star;
}
- (instancetype)initWithId:(id)obj;
//- (void)test;
@end
//TestTool.m
- (instancetype)initWithId:(id)obj
{
star = [obj copy];
return self;
}
請(qǐng)注意這里我們沒有定義test方法。如果我們對(duì)TestTool對(duì)象調(diào)用該方法,毫無疑問會(huì)崩潰的。(請(qǐng)注意這里編譯器如何不報(bào)錯(cuò),使用id對(duì)象)
這時(shí)重定向的方法可以起作用了,在TestTool.m中加入以下方法:
//如果可以在m文件內(nèi)部捕捉到這些方法則這些都不會(huì)調(diào)用
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"調(diào)用了forwardInvocation方法");
[anInvocation invokeWithTarget:star];
}
//方法簽名需要和NSInvocation聯(lián)合使用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
//對(duì)于NSObject的字類,如果頭文件中沒有聲明該方法,那么直接編譯錯(cuò)誤
//而如果頭文件聲明,但是在m文件中找不到實(shí)現(xiàn),則會(huì)調(diào)用該方法,從而有機(jī)會(huì)使用別的對(duì)象來實(shí)現(xiàn)
NSLog(@"檢驗(yàn)本類中該函數(shù)的簽名");
NSMethodSignature *sig;
if ([star methodSignatureForSelector:aSelector])
{
//如果這里不把方法簽名傳到star上也會(huì)報(bào)錯(cuò)的,test方法找不到實(shí)現(xiàn)的目標(biāo)
sig = [star methodSignatureForSelector:aSelector];
}
return sig;
}
很明顯,在methodSignatureForSelector:中,我們將試圖把star的方法簽名賦予Target;而forwardInvocation:則指定了方法實(shí)現(xiàn)的目標(biāo)。兩者缺一不可。
接著我們?cè)囍y(cè)試它,先定義重定向的目標(biāo)類,當(dāng)然它最好有test方法了。
//Another.h
@interface Another : NSObject<NSCopying>
- (void)test;
@end
- (void)test
{
NSLog(@"這里實(shí)現(xiàn)了test方法");
}
- (id)copyWithZone:(NSZone *)zone
{
return [[[self class] allocWithZone:zone] init];
}
OK,現(xiàn)在我們可以正式去看看重定向的結(jié)果了:
Another *another = [Another new];
id tool = [[TestTool alloc] initWithId:another];
[tool test];
可以看到打印的結(jié)果:這里實(shí)現(xiàn)了test方法.
3.注意
上面值得注意的一點(diǎn)是記得TargetProxy或者其他對(duì)象初始化時(shí)返回的是id,而不直接指定為該類的對(duì)象,否則編譯器將去該類的父類簇的方法鏈表中尋找該方法,導(dǎo)致直接編譯出錯(cuò),這樣連消息重定向的機(jī)會(huì)都沒有了。
4.再看看
本來研究到這里告一段落了,和我之前留下的印象差不多。但是看到NSObject中的一些方法,忍不住拿來玩了一下。
+ (BOOL)instancesRespondToSelector:(SEL)aSelector
{
NSLog(@"調(diào)用instancesRespondToSelector");
return YES;
}
+ (BOOL)conformsToProtocol:(Protocol *)protocol
{
NSLog(@"調(diào)用conformsToProtocol");
return YES;
}
- (IMP)methodForSelector:(SEL)aSelector
{
NSLog(@"調(diào)用methodForSelector");
IMP imp = [self methodForSelector:aSelector];
return imp;
}
+ (IMP)instanceMethodForSelector:(SEL)aSelector
{
NSLog(@"調(diào)用instanceMethodForSelector");
IMP imp = [self instanceMethodForSelector:aSelector];
return imp;
}
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"調(diào)用doesNotRecognizeSelector");
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"調(diào)用forwardingTargetForSelector");
return self;
}
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *sig = [self instanceMethodSignatureForSelector:aSelector];
NSLog(@"調(diào)用instanceMethodSignatureForSelector");
return sig;
}
大多數(shù)方法含義是明確的,但是確實(shí)發(fā)現(xiàn)forwardingTargetForSelector:這個(gè)方法會(huì)在消息轉(zhuǎn)移之前被調(diào)用。
查閱了一些資料后,它的作用是可以直接將要轉(zhuǎn)發(fā)的對(duì)象返回。你應(yīng)該有辦法去嘗試這個(gè)方法的作用。
A如果想要把一封信交給B,可以直接把B叫到家里來;也可以給B送過去,這個(gè)機(jī)制可謂相當(dāng)厲害了。
在以上3個(gè)方法都沒有找到消息接收者的情況下,系統(tǒng)大概也沒辦法了,只能給你最后一次補(bǔ)救的機(jī)會(huì):
- (void)doesNotRecognizeSelector:(SEL)aSelector;
我沒能找到這個(gè)方法,你自己看著辦吧,我要拋出異常了。好吧,到這里應(yīng)用基本上就會(huì)崩潰了。
5.小結(jié)
最后總結(jié)一下呢,就是調(diào)用方法的消息發(fā)出后,先去該類及類別的方法鏈表中去找,找不到就去找父類了;直到最后找到基類:NSObject.
然后會(huì)依次調(diào)用下面幾個(gè)方法(在沒有消息響應(yīng)者的情況下):
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (void)doesNotRecognizeSelector:(SEL)aSelector;
另外補(bǔ)一句,使用try...catch方法也可以不崩潰,不過個(gè)人不是特別感冒這個(gè)。問題隱藏了不代表不存在,不是嗎?