天天看點

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個方法都沒有找到消息接收者的情況下,系統大概也沒辦法了,隻能給你最後一次補救的機會:

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

5.小結

最後總結一下呢,就是調用方法的消息發出後,先去該類及類别的方法連結清單中去找,找不到就去找父類了;直到最後找到基類:NSObject.

然後會依次調用下面幾個方法(在沒有消息響應者的情況下):

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

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