重拾iOS-NSProxy

image

關(guān)鍵詞:NSProxy,NSObject,Runtime

面試題:

1)知道NSProxy嗎苟鸯?

2)NSProxy和NSObject的區(qū)別是什么问欠?

3)在開(kāi)發(fā)中NSProxy有哪些運(yùn)用場(chǎng)景稽荧?

一擦盾、什么是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是一個(gè)抽象的超類鳄袍,為充當(dāng)其他對(duì)象或尚不存在的對(duì)象的代理對(duì)象定義API窗怒。通常映跟,發(fā)送給代理的消息被轉(zhuǎn)發(fā)到實(shí)際對(duì)象,或者導(dǎo)致代理加載(或轉(zhuǎn)換為)真實(shí)對(duì)象扬虚。NSProxy的子類可用于實(shí)現(xiàn)透明的分布式消息傳遞(例如努隙,NSDistantObject)或用于延遲實(shí)例化創(chuàng)建代價(jià)高昂的對(duì)象。

NSProxy 是一個(gè)類似于NSObject的基類辜昵,是一等公民荸镊。

NS_ROOT_CLASS
@interface NSProxy <NSObject>{
    Class   isa;
}

二、NSProxy的用法

NSProxy實(shí)現(xiàn)了包括NSObject協(xié)議在內(nèi)基類所需的基礎(chǔ)方法,但是作為一個(gè)抽象的基類并沒(méi)有提供初始化的方法躬存。它接收到任何自己沒(méi)有定義的方法他都會(huì)產(chǎn)生一個(gè)異常收厨,所以一個(gè)實(shí)際的子類必須提供一個(gè)初始化方法或者創(chuàng)建方法,并且重載forwardInvocation:方法和methodSignatureForSelector:方法來(lái)處理自己沒(méi)有實(shí)現(xiàn)的消息优构。這也是NSProxy的主要功能诵叁,負(fù)責(zé)把消息轉(zhuǎn)發(fā)給真正的target的代理類,NSProxy正是代理的意思钦椭。

創(chuàng)建一個(gè)SFProxy類繼承于NSProxy:

// .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];
}

// 消息轉(zhuǎn)發(fā)
- (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

使用場(chǎng)景:

1拧额、解決NSTimer/CADisplayLink的循環(huán)引用問(wèn)題

NSTimer是一個(gè)需要添加到Runloop里的類,對(duì)于一個(gè)不會(huì)自動(dòng)停止的Timer彪腔,你需要調(diào)用invalidate方法來(lái)手動(dòng)斷開(kāi)這個(gè)Timer侥锦。否則,引用Timer的Controller或者其他類德挣,就會(huì)出現(xiàn)循環(huán)引用而無(wú)法釋放掉恭垦。

比如:

@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這樣一個(gè)ViewController,然后pop。

你會(huì)發(fā)現(xiàn)Controller沒(méi)有被釋放格嗅,timer也沒(méi)有被取消番挺。

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

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

因?yàn)镃ontroller根本沒(méi)有被釋放屯掖,dealloc方法根本不會(huì)調(diào)用玄柏。

image

如圖,因?yàn)镹STimer內(nèi)部target屬性是強(qiáng)引用贴铜,所以當(dāng)SFViewController強(qiáng)引用NSTimer粪摘,并且將NSTimer的target設(shè)置為SFViewController時(shí),就產(chǎn)生了循環(huán)引用绍坝。

使用NSProxy就可以打破這種循環(huán)徘意。

image

使用方法:

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

2、模擬多繼承

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

方法調(diào)用:

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]

三椎咧、NSProxy和NSObject的區(qū)別

雖然NSProxy和class NSObject都定義了-forwardInvocation:-methodSignatureForSelector:,但這兩個(gè)方法并沒(méi)有在protocol NSObject中聲明灾挨;兩者對(duì)這倆方法的調(diào)用邏輯更是完全不同邑退。

對(duì)于class NSObject而言竹宋,接收到消息后先去自身的方法列表里找匹配的selector劳澄,如果找不到,會(huì)沿著繼承體系去superclass的方法列表找蜈七;如果還找不到秒拔,先后會(huì)經(jīng)過(guò)+resolveInstanceMethod:-forwardingTargetForSelector:處理,處理失敗后飒硅,才會(huì)到-methodSignatureForSelector:/-forwardInvocation:進(jìn)行最后的掙扎.

但對(duì)于NSProxy砂缩,接收unknown selector后作谚,直接回調(diào)-methodSignatureForSelector:/-forwardInvocation:,消息轉(zhuǎn)發(fā)過(guò)程比class NSObject要簡(jiǎn)單得多庵芭。

相對(duì)于class NSObject妹懒,NSProxy的另外一個(gè)非常重要的不同點(diǎn)也值得注意:NSProxy會(huì)將自省相關(guān)的selector直接forward到-forwardInvocation:回調(diào)中,這些自省方法包括:

- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;

簡(jiǎn)單來(lái)說(shuō)双吆,這4個(gè)selector的實(shí)際接收者realObject眨唬,而不是NSProxy對(duì)象本身。但另一方面好乐,NSProxy并沒(méi)有將performSelector系列selector也forward到-forwardInvocation:匾竿,換句話說(shuō),[proxy performSelector:someSelector]的真正處理者仍然是proxy自身蔚万,只是后續(xù)會(huì)將someSelector給forward到-forwardInvocation:回調(diào)岭妖,然后經(jīng)由realObject處理。

相關(guān)參考:

NSProxy使用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末反璃,一起剝皮案震驚了整個(gè)濱河市昵慌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌淮蜈,老刑警劉巖废离,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異礁芦,居然都是意外死亡蜻韭,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門柿扣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)肖方,“玉大人,你說(shuō)我怎么就攤上這事未状「┗” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵司草,是天一觀的道長(zhǎng)艰垂。 經(jīng)常有香客問(wèn)我,道長(zhǎng)埋虹,這世上最難降的妖魔是什么猜憎? 我笑而不...
    開(kāi)封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮搔课,結(jié)果婚禮上胰柑,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好柬讨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布崩瓤。 她就那樣靜靜地躺著,像睡著了一般踩官。 火紅的嫁衣襯著肌膚如雪却桶。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天蔗牡,我揣著相機(jī)與錄音肾扰,去河邊找鬼。 笑死蛋逾,一個(gè)胖子當(dāng)著我的面吹牛集晚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播区匣,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼偷拔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了亏钩?” 一聲冷哼從身側(cè)響起莲绰,我...
    開(kāi)封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎姑丑,沒(méi)想到半個(gè)月后蛤签,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栅哀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年震肮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片留拾。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡戳晌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出痴柔,到底是詐尸還是另有隱情沦偎,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布咳蔚,位于F島的核電站豪嚎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谈火。R本人自食惡果不足惜侈询,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望堆巧。 院中可真熱鬧妄荔,春花似錦、人聲如沸谍肤。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)荒揣。三九已至篷角,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間系任,已是汗流浹背恳蹲。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留俩滥,地道東北人嘉蕾。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像霜旧,于是被迫代替她去往敵國(guó)和親错忱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354