重拾iOS-NSProxy

關鍵詞:NSProxy,NSObject,Runtime面試

面試題:
1)知道NSProxy嗎?
2)NSProxy和NSObject的區別是什麼?
3)在開發中NSProxy有哪些運用場景?
bash

1、什麼是NSProxy

NSProxy is an abstract superclass defining an API for objects that act as stand-ins for other objects or for objects that don’t exist yet. Typically, a message to a proxy is forwarded to the real object or causes the proxy to load (or transform itself into) the real object. Subclasses of NSProxy can be used to implement transparent distributed messaging (for example, NSDistantObject) or for lazy instantiation of objects that are expensive to create.分佈式

NSProxy是一個抽象的超類,爲充當其餘對象或尚不存在的對象的代理對象定義API。一般,發送給代理的消息被轉發到實際對象,或者致使代理加載(或轉換爲)真實對象。NSProxy的子類可用於實現透明的分佈式消息傳遞(例如,NSDistantObject)或用於延遲實例化建立代價高昂的對象。oop

NSProxy 是一個相似於NSObject的基類,是一等公民。post

NS_ROOT_CLASS
@interface NSProxy <NSObject>{
    Class   isa;
}
複製代碼

2、NSProxy的用法

NSProxy實現了包括NSObject協議在內基類所需的基礎方法,可是做爲一個抽象的基類並無提供初始化的方法。它接收到任何本身沒有定義的方法他都會產生一個異常,因此一個實際的子類必須提供一個初始化方法或者建立方法,而且重載forwardInvocation:方法和methodSignatureForSelector:方法來處理本身沒有實現的消息。這也是NSProxy的主要功能,負責把消息轉發給真正的target的代理類,NSProxy正是代理的意思。ui

建立一個SFProxy類繼承於NSProxy:atom

// .h
@interface SFProxy : NSProxy
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end

// .m
#import "SFProxy.h"

@interface SFProxy ()
@property (nonatomic, weak, readonly) NSObject *target;
@end

@implementation SFProxy
- (instancetype)initWithTarget:(id)target {
    _target = target;
    return self;
}
+ (instancetype)proxyWithTarget:(id)target {
    return [[self alloc] initWithTarget:target];
}

// 消息轉發
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (self.target && [self.target respondsToSelector:aSelector]) {
        return [self.target methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    SEL aSelector = [anInvocation selector];
    if (self.target && [self.target respondsToSelector:aSelector]) {
        [anInvocation invokeWithTarget:self.target];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

@end
複製代碼

使用場景:spa

一、解決NSTimer/CADisplayLink的循環引用問題

NSTimer是一個須要添加到Runloop裏的類,對於一個不會自動中止的Timer,你須要調用invalidate方法來手動斷開這個Timer。不然,引用Timer的Controller或者其餘類,就會出現循環引用而沒法釋放掉。代理

好比:code

@interface SFViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation SFViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerEvent) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)timerEvent{
    NSLog(@"%s",__func__);
}
- (void)dealloc{
    NSLog(@"%s",__func__);
}

@end
複製代碼

假如我Push這樣一個ViewController,而後pop。
你會發現Controller沒有被釋放,timer也沒有被取消。

即便是在dealloc中[self.timer invalidate]也不行。

- (void)dealloc{
    NSLog(@"%s",__func__);
    [self.timer invalidate];
}
複製代碼

由於Controller根本沒有被釋放,dealloc方法根本不會調用。

如圖,由於NSTimer內部target屬性是強引用,因此當SFViewController強引用NSTimer,而且將NSTimer的target設置爲SFViewController時,就產生了循環引用。

使用NSProxy就能夠打破這種循環。

使用方法:

self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[SFProxy proxyWithTarget:self] selector:@selector(timerEvent) userInfo:nil repeats:YES];
複製代碼

二、模擬多繼承

SFProxy:

// .h
@interface SFProxy : NSProxy
-(void)transformToObject:(NSObject *)obj;
@end

// .m
#import "SFProxy.h"

@interface SFProxy ()
@property (nonatomic, strong) NSObject *obj;
@end

@implementation SFProxy
-(void)transformToObject:(NSObject *)obj {
    self.obj = obj;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (self.obj && [self.obj respondsToSelector:aSelector]) {
        return [self.obj methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    SEL aSelector = [anInvocation selector];
    if (self.obj && [self.obj respondsToSelector:aSelector]) {
        [anInvocation invokeWithTarget:self.obj];
    }
    else {
        [super forwardInvocation:anInvocation];
    }
}

@end
複製代碼

方法調用:

ClassA *clsA = [[ClassA alloc]init];
ClassB *clsB = [[ClassB alloc]init];
SFProxy *proxy = [SFProxy alloc];
// 變身爲clsA的代理
[proxy transformToObject:clsA];
[proxy performSelector:@selector(funcA) withObject:nil];
// 變身爲clsB的代理
[proxy transformToObject:clsB];
[proxy performSelector:@selector(funcB) withObject:nil];
複製代碼

打印:

2020-07-04 12:44:49.893660+0800 OCTestDemo[71379:1458448] -[ClassA funcA]
2020-07-04 12:44:49.893836+0800 OCTestDemo[71379:1458448] -[ClassB funcB]
複製代碼

3、NSProxy和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處理。

相關參考:
NSProxy使用

相關文章
相關標籤/搜索