詳解NSProxy 一、概述 二、基本作用 三、實際應用 四、與NSObject的區別

一、概述

        NSProxy是少有的沒有從NSObject類繼承的類,它實現了NSObject協議。其日常使用不多,但是也是有一些比較好的使用場景。

// 實現了NSObject協議
@interface NSProxy <NSObject> {
    __ptrauth_objc_isa_pointer Class    isa;
}

+ (id)alloc;
+ (id)allocWithZone:(nullable NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE;
+ (Class)class;

- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
- (void)dealloc;
- (void)finalize;
@property (readonly, copy) NSString *description;
@property (readonly, copy) NSString *debugDescription;
+ (BOOL)respondsToSelector:(SEL)aSelector;

- (BOOL)allowsWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);
- (BOOL)retainWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);

// - (id)forwardingTargetForSelector:(SEL)aSelector;

@end

二、基本作用

        正如該類的名字所示的那樣,這個類最基本的作用就是作爲一個代理。通過持有其他對象並自動的把消息轉發給所持有(代理)的對象類來實現代理的功能。這麼說起來,都有被代理對象了,爲什麼要代理?其實現實世界是負責的,比如代理對象的創建代價比較昂貴。另外,在這個基本作用的基礎之上可以構建更加強大的應用(本文第三小節會講到)。

/*------------頭文件-------------*/
@interface MyProxy : NSProxy

+ (instancetype)proxyWithTarget:(NSObject *)target;

@end

/*------------實現文件-------------*/
@interface MyProxy ()

@property (nonatomic, strong) NSObject *target;

@end

@implementation MyProxy

// NSProxy 沒有初始化方法,需要我們自己實現
- (instancetype)initWithTarget:(NSObject *)target {
    _target = target;
    return self;
}

+ (instancetype)proxyWithTarget:(NSObject *)target {
    MyProxy *proxy = [[MyProxy alloc] initWithTarget:target];
    return proxy;
}

// 獲得調用方法的方法簽名,必須實現的方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    NSMethodSignature *signature;
    if (_target) {
        signature = [_target methodSignatureForSelector:sel];
    } else {
       signature= [super methodSignatureForSelector:sel];
    }
    return signature;
}

// 爲調用設置目標,必須實現的方法
- (void)forwardInvocation:(NSInvocation *)invocation {
    if (!_target) {
        return;
    }
        
    [invocation invokeWithTarget:_target];
}

@end

/*------------測試代碼-------------*/
NSString *aStr = (NSString *)[MyProxy proxyWithTarget:@"here"];
[aStr length];

// 看起來測試代碼平淡無奇,但是需要注意的是:aStr能夠響應不屬於自己的消息
// aStr 雖然進行了類型轉換,但是它實質上是MyProxy類型

三、實際應用

3.1 解除循環引用

        我們都知道NSTimer引起循環引用的場景,以VC含有Timer,Timer持有VC爲例,循環引用如下圖所示。



        解決此問題的方法也很簡單,使用如下代碼。

/*------------頭文件-------------*/
@interface MyProxy : NSProxy

+ (instancetype)proxyWithTarget:(NSObject *)target;

@end

/*------------實現文件-------------*/
@interface MyProxy ()

@property (nonatomic, weak) NSObject *target;

@end

@implementation MyProxy

// NSProxy 沒有初始化方法,需要我們自己實現
- (instancetype)initWithTarget:(NSObject *)target {
    _target = target;
    return self;
}

+ (instancetype)proxyWithTarget:(NSObject *)target {
    MyProxy *proxy = [[MyProxy alloc] initWithTarget:target];
    return proxy;
}

// 獲得調用方法的方法簽名,必須實現的方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    NSMethodSignature *signature;
    if (_target) {
        signature = [_target methodSignatureForSelector:sel];
    } else {
       signature= [super methodSignatureForSelector:sel];
    }
    return signature;
}

// 爲調用設置目標,必須實現的方法
- (void)forwardInvocation:(NSInvocation *)invocation {
    if (!_target) {
        return;
    }
        
    [invocation invokeWithTarget:_target];
}

@end

/*------------實現文件-------------*/

// 創建timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[MyProxy proxyWithTarget:self] selector:@selector(timerEvent) userInfo:nil repeats:YES];

// 銷燬timer
-(void)dealloc {
  [_timer invalidate];
}

        源碼之前了無祕密!其解除循環依賴本質上依賴的是weak,但是由於其具有消息轉發作用,所以對原有代碼邏輯改動很小!

3.2 模擬多繼承

        模擬多繼承也是很簡單的,代碼如下:

ClassA *clsA = [[ClassA alloc]init];
ClassB *clsB = [[ClassB alloc]init];

MyProxy *proxy = [SFProxy alloc];
// 變身爲clsA的代理
[proxy transformToObject:clsA];
[proxy performSelector:@selector(funcA) withObject:nil];

// 變身爲clsB的代理
[proxy transformToObject:clsB];
[proxy performSelector:@selector(funcB) withObject:nil];

四、與NSObject的區別

        雖然NSProxy和class NSObject都定義了-forwardInvocation:和-methodSignatureForSelector:,但這兩個方法並沒有在protocol NSObject中聲明;兩者對這倆方法的調用邏輯更是完全不同。
        對於class NSObject而言,接收到消息後先去自身的方法列表裏找匹配的selector,如果找不到,會沿着繼承體系去superclass的方法列表找;如果還找不到,先後會經過+resolveInstanceMethod:和-forwardingTargetForSelector:處理,處理失敗後,纔會到-methodSignatureForSelector:/-forwardInvocation:進行最後的掙扎.
        但對於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處理。

參考如下文章:
1、https://juejin.cn/post/6847902216209039367
2、https://juejin.cn/post/6844903922008604686

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章