NSObject,NSProxy以及異常處理

參考資料:
https://my.oschina.net/iq19900204/blog/411450

http://blog.csdn.net/devday/article/details/7418022

http://ios.jobbole.com/87856/

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è)。問題隱藏了不代表不存在,不是嗎?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容