NSProxy與定時(shí)器, 解決循環(huán)引用

前言

今天看別人的代碼, 發(fā)現(xiàn)用到了NSProxy這個(gè)類, 就查了一下, 然后就發(fā)現(xiàn), 自己用了這么久的定時(shí)器NSTimer, 居然大部分都會(huì)有內(nèi)存問題, 就覺得必須記錄一下, 如果你也像我一樣用的NSTimer, 那你可能就要注意了, 請(qǐng)看如下問題代碼:

@property (nonatomic, weak) NSTimer *timer;
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    // 定時(shí)器 重不重復(fù)沒影響
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerRunning) userInfo:nil repeats:YES];
    // 這句話 是為了讓滑動(dòng)scrollView的時(shí)候定時(shí)器不會(huì)停止, 加不加對(duì)今天的問題沒影響
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)timerRunning
{
    NSLog(@"1");
}
- (void)dealloc
{
    NSLog(@"FirstViewController --dealloc");
    [self.timer invalidate];
}

上面的用法是有問題的, NSTimer必須要調(diào)用invalidate方法, 才能釋放, 然而上面的- dealloc方法就不會(huì)走, 所以定時(shí)器也不會(huì)釋放。(不信的可以親自試試, 一定要用target-action的模式, 用block是不會(huì)出現(xiàn)這個(gè)問題的, 想知道為什么, 繼續(xù)往下看)

無法釋放的原因如下原因如下:

其實(shí)產(chǎn)生循環(huán)引用的根本就是引用計(jì)數(shù), 然而上面的情況并不僅僅是循環(huán)引用, 如果不用屬性保存NSTimer, pop控制器后, 依然會(huì)造成定時(shí)器在后臺(tái)打印。 我們用引用計(jì)數(shù)來解釋原因:

  • 控制器push或者present過來, 控制器的引用計(jì)數(shù)+1
  • 添加控制器相當(dāng)于在Runloop中注冊(cè)timer, Runloop會(huì)強(qiáng)引用定時(shí)器
  • 定時(shí)器通過target-action的方式引用控制器, target-action的設(shè)計(jì)模式中, 對(duì)target的引用應(yīng)該是弱引用的, 為什么會(huì)造成強(qiáng)引用, 我猜測(cè)(知道真相的小伙伴可以留言告訴我)可能是NSTimer把控制器交給runloop進(jìn)行強(qiáng)引用, 以便于在到達(dá)注冊(cè)時(shí)間時(shí)發(fā)送消息, 因此即便用弱引用的weakSelf修飾控制器, 依然無法解決內(nèi)存無法釋放的問題, 因?yàn)槟憧刂破鞯闹羔槀鞯搅藃unloop手里, runloop就將控制器的引用計(jì)數(shù)+1了拆讯。
  • 當(dāng)pop或者dismiss的時(shí)候, 控制器引用計(jì)數(shù)-1, 然后界面消失, 你就再也找不到控制器了, 然而控制器的引用計(jì)數(shù)還有1呢, 控制器的內(nèi)存就泄露啊, 沒有被銷毀; 因?yàn)?code>引用計(jì)數(shù)從1到0的時(shí)候才會(huì)調(diào)用dealloc方法, 因此, 定時(shí)器也沒有被invalidate, 它會(huì)在后臺(tái)一直循環(huán)打印, 不勝其煩!

那么這個(gè)問題怎么解決呢?

我之前的解決方式是, 在viewDidDisappear的時(shí)候調(diào)用invalidate, 雖然解決了問題, 但是還是有新問題的, 因?yàn)椴恢故窍У臅r(shí)候viewDidDisappear會(huì)走, pushpresent控制器的時(shí)候viewDidDisappear也會(huì)走啊, 那么怎么辦?

NSProxy就是你的曙光了

NSProxy是iOS開發(fā)中一個(gè)消息轉(zhuǎn)發(fā)的基類驯耻,它不繼承自NSObject。因?yàn)樗彩?code>Foundation框架中的基類, 通常用來實(shí)現(xiàn)消息轉(zhuǎn)發(fā), 我們也可以用它來包裝控制器, 達(dá)到弱引用的效果崔慧。PS: 如果你只是想要解決以上的問題, 可以完全不用理解消息轉(zhuǎn)發(fā)機(jī)制, 直接使用代碼就夠了, 用法超級(jí)簡(jiǎn)單; 如果你想了解, 請(qǐng)點(diǎn)擊這里

NSProxy是一個(gè)抽象類, 需要使用它的子類, 然后需要實(shí)現(xiàn)init以及消息轉(zhuǎn)發(fā)的相關(guān)方法拂蝎。

// 當(dāng)一個(gè)消息轉(zhuǎn)發(fā)的動(dòng)作NSInvocation到來的時(shí)候,在這里選擇把消息轉(zhuǎn)發(fā)給對(duì)應(yīng)的實(shí)際處理對(duì)象
- (void)forwardInvocation:(NSInvocation *)anInvocation

// 當(dāng)一個(gè)SEL到來的時(shí)候惶室,在這里返回SEL對(duì)應(yīng)的NSMethodSignature
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

// 是否響應(yīng)一個(gè)SEL
+ (BOOL)respondsToSelector:(SEL)aSelector

首先創(chuàng)建一個(gè)NSProxy的子類WeakProxy, 并在.h文件中聲明以下屬性和方法

@interface WeakProxy : NSProxy

@property (weak,nonatomic,readonly)id target;
+ (instancetype)proxyWithTarget:(id)target;
- (instancetype)initWithTarget:(id)target;

@end

.m里實(shí)現(xiàn)如下

@implementation WeakProxy

- (instancetype)initWithTarget:(id)target{
    _target = target;
    return self;
}
+ (instancetype)proxyWithTarget:(id)target{
    return [[self alloc] initWithTarget:target];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
    SEL sel = [invocation selector];
    if ([self.target respondsToSelector:sel]) {
        [invocation invokeWithTarget:self.target];
    }
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [self.target methodSignatureForSelector:aSelector];
}
- (BOOL)respondsToSelector:(SEL)aSelector{
    return [self.target respondsToSelector:aSelector];
}

@end

以上代碼就可以用了, 用法如下:


@property (nonatomic, weak) NSTimer *timer;

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[WeakProxy proxyWithTarget:self] selector:@selector(timerRunning) userInfo:nil repeats:YES];
}

- (void)timerRunning
{
    NSLog(@"1");
}
- (void)dealloc
{
    NSLog(@"FirstViewController --dealloc");
    [self.timer invalidate];
}

以上, 我們就解決了定時(shí)器不釋放的問題, 解決原理如下:

我們依然從引用計(jì)數(shù)的角度分析:

  • pushpresent進(jìn)入控制器, 引用計(jì)數(shù)+1
  • 定時(shí)器向NSRunloop注冊(cè)事件, NSRunloop強(qiáng)引用WeakProxy對(duì)象, WeakProxy弱引用控制器(因?yàn)槭侨跻? 引用計(jì)數(shù)不變), 當(dāng)?shù)竭_(dá)定時(shí)器注冊(cè)的時(shí)間時(shí), Runloop會(huì)向WeakProxy對(duì)象發(fā)送消息, WeakProxy觸發(fā)消息轉(zhuǎn)發(fā), 把消息轉(zhuǎn)發(fā)給弱引用的控制器處理
  • 當(dāng)popdismiss控制器時(shí), 引用計(jì)數(shù)-1 變?yōu)?觸發(fā)-dealloc方法, 調(diào)用定時(shí)器的invalidate注銷了定時(shí)器, 同時(shí)Runloop注銷掉對(duì)WeakProxy對(duì)象的強(qiáng)引用, 至此, 所有的內(nèi)存都被釋放了, 問題就解決了
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末温自,一起剝皮案震驚了整個(gè)濱河市玄货,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悼泌,老刑警劉巖松捉,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異馆里,居然都是意外死亡隘世,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門鸠踪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丙者,“玉大人,你說我怎么就攤上這事营密÷樱” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵卵贱,是天一觀的道長(zhǎng)滥沫。 經(jīng)常有香客問我,道長(zhǎng)键俱,這世上最難降的妖魔是什么兰绣? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮编振,結(jié)果婚禮上缀辩,老公的妹妹穿的比我還像新娘。我一直安慰自己踪央,他們只是感情好臀玄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著畅蹂,像睡著了一般健无。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上液斜,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天累贤,我揣著相機(jī)與錄音,去河邊找鬼少漆。 笑死臼膏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的示损。 我是一名探鬼主播渗磅,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了始鱼?” 一聲冷哼從身側(cè)響起论巍,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎风响,沒想到半個(gè)月后嘉汰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡状勤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年鞋怀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片持搜。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡密似,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出葫盼,到底是詐尸還是另有隱情残腌,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布贫导,位于F島的核電站抛猫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏孩灯。R本人自食惡果不足惜闺金,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望峰档。 院中可真熱鬧败匹,春花似錦、人聲如沸讥巡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽欢顷。三九已至槽棍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吱涉,已是汗流浹背刹泄。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工外里, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怎爵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓盅蝗,卻偏偏與公主長(zhǎng)得像鳖链,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容