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是一個虛類,你可以通過繼承它,并重載下面兩個方法以實現將消息轉發到另一個實體。

  • (void)forwardInvocation:(NSInvocation *)invocation;
  • (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");

這里最好描述一下虛類的概念:虛類又叫做抽象類,這個類主要定義一些方法然后讓子類去實現。正如有人描述的,動物是一個大概念,但是通常情況下你不會去定義動物的對象;而是先產生繼承的字類貓啊,狗啊的,再去實例化。這個動物就可以用虛類來表示了,畢竟動物是有共性的。以上純屬個人理解,非官方語言描述!
NSObject既是對象的基類,又是一種協議。它的頭文件是這樣的:

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

而NSObject協議的定義是這樣的:

#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一個虛類,但是同時它也實現了NSObject協議,它的定義是這樣的:

@interface NSProxy <NSObject> {
    Class   isa;
}

所以NSProxy的字類可以實現NSObject協議中的一些方法。

2. 方法重定向

比較優秀的iOS工程師應該都知道對象在調用方法時的機制,會尋找當前類的方法緩存,方法鏈表;然后依次向父類去尋找,一直到NSObject,到了NSobject還是找不到的話,我們有機會使用上面的方法來轉移方法的注意力了。
到這里我本來想引用另一個博客中定義了NSProxy父類的方法,但是博主不允許隨便轉載,大家有空自己去看吧。
http://blog.csdn.net/devday/article/details/7418022
我們先定義一個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;
}

請注意這里我們沒有定義test方法。如果我們對TestTool對象調用該方法,毫無疑問會崩潰的。(請注意這里編譯器如何不報錯,使用id對象)
這時重定向的方法可以起作用了,在TestTool.m中加入以下方法:

//如果可以在m文件內部捕捉到這些方法則這些都不會調用
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"調用了forwardInvocation方法");
    [anInvocation invokeWithTarget:star];
}

//方法簽名需要和NSInvocation聯合使用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    //對于NSObject的字類,如果頭文件中沒有聲明該方法,那么直接編譯錯誤
    //而如果頭文件聲明,但是在m文件中找不到實現,則會調用該方法,從而有機會使用別的對象來實現
    NSLog(@"檢驗本類中該函數的簽名");
    NSMethodSignature *sig;
    if ([star methodSignatureForSelector:aSelector])
    {
        //如果這里不把方法簽名傳到star上也會報錯的,test方法找不到實現的目標
        sig = [star methodSignatureForSelector:aSelector];
    }
    return sig;
}

很明顯,在methodSignatureForSelector:中,我們將試圖把star的方法簽名賦予Target;而forwardInvocation:則指定了方法實現的目標。兩者缺一不可。
接著我們試著去測試它,先定義重定向的目標類,當然它最好有test方法了。

//Another.h
@interface Another : NSObject<NSCopying>

- (void)test;
@end
- (void)test
{
    NSLog(@"這里實現了test方法");
}

- (id)copyWithZone:(NSZone *)zone
{
    return [[[self class] allocWithZone:zone] init];
}

OK,現在我們可以正式去看看重定向的結果了:

Another *another = [Another new];
id tool = [[TestTool alloc] initWithId:another];
[tool test];

可以看到打印的結果:這里實現了test方法.

3.注意

上面值得注意的一點是記得TargetProxy或者其他對象初始化時返回的是id,而不直接指定為該類的對象,否則編譯器將去該類的父類簇的方法鏈表中尋找該方法,導致直接編譯出錯,這樣連消息重定向的機會都沒有了。

4.再看看

本來研究到這里告一段落了,和我之前留下的印象差不多。但是看到NSObject中的一些方法,忍不住拿來玩了一下。

+ (BOOL)instancesRespondToSelector:(SEL)aSelector
{
    NSLog(@"調用instancesRespondToSelector");
    return YES;
}
+ (BOOL)conformsToProtocol:(Protocol *)protocol
{
    NSLog(@"調用conformsToProtocol");
    return YES;
}
- (IMP)methodForSelector:(SEL)aSelector
{
    NSLog(@"調用methodForSelector");
    IMP imp = [self methodForSelector:aSelector];
    return imp;
}
+ (IMP)instanceMethodForSelector:(SEL)aSelector
{
    NSLog(@"調用instanceMethodForSelector");
    IMP imp = [self instanceMethodForSelector:aSelector];
    return imp;
}
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    NSLog(@"調用doesNotRecognizeSelector");
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSLog(@"調用forwardingTargetForSelector");
    return self;
}

+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *sig = [self instanceMethodSignatureForSelector:aSelector];
    NSLog(@"調用instanceMethodSignatureForSelector");
    return sig;
}

大多數方法含義是明確的,但是確實發現forwardingTargetForSelector:這個方法會在消息轉移之前被調用。
查閱了一些資料后,它的作用是可以直接將要轉發的對象返回。你應該有辦法去嘗試這個方法的作用。
A如果想要把一封信交給B,可以直接把B叫到家里來;也可以給B送過去,這個機制可謂相當厲害了。
在以上3個方法都沒有找到消息接收者的情況下,系統大概也沒辦法了,只能給你最后一次補救的機會:

- (void)doesNotRecognizeSelector:(SEL)aSelector;

我沒能找到這個方法,你自己看著辦吧,我要拋出異常了。好吧,到這里應用基本上就會崩潰了。

5.小結

最后總結一下呢,就是調用方法的消息發出后,先去該類及類別的方法鏈表中去找,找不到就去找父類了;直到最后找到基類:NSObject.
然后會依次調用下面幾個方法(在沒有消息響應者的情況下):

- (id)forwardingTargetForSelector:(SEL)aSelector;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (void)doesNotRecognizeSelector:(SEL)aSelector;

另外補一句,使用try...catch方法也可以不崩潰,不過個人不是特別感冒這個。問題隱藏了不代表不存在,不是嗎?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容