使用 NSProxy 實現訊息轉發

任淏發表於2022-07-04

一、簡介

​ 在 iOS 應用開發中,自定義一個類一般需要繼承自 NSObject 類或者 NSObject 子類,但是,NSProxy 類不是繼承自 NSObject 類或者 NSObject 子類,而是一個實現了 NSObject 協議的抽象基類。

/*	NSProxy.h
	Copyright (c) 1994-2019, Apple Inc. All rights reserved.
*/

#import <Foundation/NSObject.h>

@class NSMethodSignature, NSInvocation;

NS_ASSUME_NONNULL_BEGIN

NS_ROOT_CLASS
@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

NS_ASSUME_NONNULL_END

NSProxy 的作用就是作為一個委託代理物件,將訊息轉發給一個真實的物件或者自己載入的物件。

為了進一步瞭解 NSProxy 類的作用,我們來實現一個同事呼叫 NSMutableString 和 NSMutableArray 兩個類中的方法的委託類,模擬多繼承。

首先建立 TargetProxy 類,讓他繼承 NSProxy。並實現初始化方法。

@interface TargetProxy : NSProxy


/// 初始化方法,儲存兩個真實物件
/// @param object1 第一個真實物件
/// @param object2 第二個真實物件
- (instancetype)initWithObject1:(id)object1 object2:(id)object2;

@end
@implementation TargetProxy {

    // 儲存需要將訊息轉發到的第一個真實物件
    // 第一個真實物件的方法呼叫優先順序會比第二個真實物件的方法呼叫優先順序高
    id _realObject1;
    // 儲存需要將訊息轉發到的第二個真實物件
    id _realObject2;
}

- (instancetype)initWithObject1:(id)object1 object2:(id)object2 {
    _realObject1 = object1;
    _realObject2 = object2;
    
    return self;
}

然後在 TargetProxy.m 檔案中,重寫 - methodSignatureForSelector: 獲取真實物件方法簽名,並重寫 - forwardInvocation: 方法,呼叫真實的物件方法。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    // 獲取 _realObject1 中 sel 的方法簽名
    NSMethodSignature *signature = [_realObject1 methodSignatureForSelector:sel];
    // 如果 _realObject1 中有該方法,那麼返回該方法的簽名
    // 如果沒有,返回 _realObject1 方法簽名
    if (signature) {
        return signature;
    }
    // 獲取 _realObject1 中的 sel 的方法簽名
    signature = [_realObject2 methodSignatureForSelector:sel];
    return signature;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    // 獲取擁有該方法的真實物件
    id target = [_realObject1 methodSignatureForSelector:[invocation selector]] ? _realObject1 : _realObject2;

    // 執行方法
    [invocation invokeWithTarget:target];
}

最後,進行 Demo 測試

- (void)testTargetProxy {
    NSMutableString *string = [NSMutableString string];
    NSMutableArray *array = [NSMutableArray array];
    
    id proxy = [[TargetProxy alloc] initWithObject1:string object2:array];
    [proxy appendString:@"This "];
    [proxy appendString:@"is "];
    [proxy addObject:string];
    [proxy appendString:@"a "];
    [proxy appendString:@"test!"];
    
    NSLog(@"The string is length is: %@", [proxy valueForKey:@"length"]);
    NSLog(@"count should be 1, it is %ld", [proxy count]);
    
    if ([[proxy objectAtIndex:0] isEqualToString:@"This is a test!"]) {
        NSLog(@"Appending successful.");
    } else {
        NSLog(@"Appending failed,, got: '%@'", proxy);
    }
}

執行上面的程式碼,輸入日誌如下:

2022-04-02 11:30:35.957145+0800 Demo[19783:586710] SuccessFully create Delegere Proxy automatically.
2022-04-02 11:30:35.959722+0800 Demo[19783:586710] The string is length is: 15
2022-04-02 11:30:35.960175+0800 Demo[19783:586710] count should be 1, it is 1
2022-04-02 11:30:40.086227+0800 Demo[19783:586710] Appending successful.

​ 以上說明,我們使用 TargetProxy 類成功的實現了訊息轉發。

​ 當然,在大部分情況下,使用 NSObject 類也可以實現訊息轉發,實現方式和 NSProxy 類似,但是大部分情況下使用 NSProxy 更加合適。因為:

  • NSProxy 類實現了包括 NSObject 協議在內基類所需的基礎方法
  • 通過 NSObject 類實現的代理類不會自動的轉發 NSObject 協議中的方法
  • 通過 NSObject 類實現的代理類不會自動的轉發 NSObject 類別中的方法

相關文章