面試題:NSTimer循環(huán)引用的解決方案

前言

在使用NSTimer荸镊,如果使用不得當(dāng)特別會(huì)引起循環(huán)引用铃慷,造成內(nèi)存泄露。所以怎么避免循環(huán)引用問題剧罩,下面我提出幾種解決NSTimer的幾種循環(huán)引用。

原因

當(dāng)你在ViewController(簡稱VC)中使用timer屬性座泳,由于VC強(qiáng)引用timer,timer的target又是VC造成循環(huán)引用幕与。當(dāng)你在VC的dealloc方法中銷毀timer挑势,
發(fā)現(xiàn)VC被pop,VC的dealloc方法沒走啦鸣,VC在等timer釋放才走dealloc潮饱,timer釋放在dealloc中,所以引起循環(huán)引用诫给。

解決方案

方案一

//1香拉、類別中使用copy block的形式使timer只引用自己而不引用TimerViewController(可以釋放)
//如果在block里面直接調(diào)用self啦扬,還是會(huì)保留環(huán)的。因?yàn)閎lock對self強(qiáng)引用凫碌,self對timer強(qiáng)引用扑毡,timer又通過userInfo參數(shù)保留block(強(qiáng)引用block),這樣就構(gòu)成一個(gè)環(huán)block->self->timer->userinfo->block,所以要打破這個(gè)環(huán)的話要在block里面弱引用self盛险。

  • (NSTimer )timerWithLFJBlockSupport{
    __weak __typeof(&
    self)weakSelf = self;
    NSTimer *timer = [NSTimer lfj_scheduledTimerWithTimeInterval:1 repeats:YES block:^{
    [weakSelf countAddWith:28];
    }];
    return timer;
    }

import "NSTimer+LFJBlockSupport.h"

@implementation NSTimer (LFJBlockSupport)
/*
該方案主要要點(diǎn):
將計(jì)時(shí)器所應(yīng)執(zhí)行的任務(wù)封裝成"Block"瞄摊,在調(diào)用計(jì)時(shí)器函數(shù)時(shí),把block作為userInfo參數(shù)傳進(jìn)去苦掘。
userInfo參數(shù)用來存放"不透明值"换帜,只要計(jì)時(shí)器有效,就會(huì)一直保留它鹤啡。
在傳入?yún)?shù)時(shí)要通過copy方法惯驼,將block拷貝到"堆區(qū)",否則等到稍后要執(zhí)行它的時(shí)候递瑰,該blcok可能已經(jīng)無效了祟牲。
計(jì)時(shí)器現(xiàn)在的target是NSTimer類對象,這是個(gè)單例泣矛,因此計(jì)時(shí)器是否會(huì)保留它疲眷,其實(shí)都無所謂。此處依然有保留環(huán)您朽,然而因?yàn)轭悓ο螅╟lass object)無需回收狂丝,所以不用擔(dān)心。
*/

  • (NSTimer*)lfj_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats block:(void(^)(void))block{

    return [self scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(lfj_blockSelector:) userInfo:[block copy] repeats:repeats];
    }
    +(void)lfj_blockSelector:(NSTimer *)timer{
    void(^block)(void) = timer.userInfo;
    if (block) {
    block();
    }
    }

@end
方案二

//2哗总、使用系統(tǒng)block(不過只支持iOS 10以后)(可以釋放)

  • (NSTimer )timerWithsystemBlock{
    __weak __typeof(&(
    self))weakSelf = self;
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    [weakSelf countAddWith:28];
    }];
    return timer;
    }
    方案三

//3几颜、使用中間類弱引用(weak)target

  • (NSTimer *)timerWithWeakTimer{
    NSTimer *timer = [LFJWeakTimer weak_lfjScheduledTimerWithTimeInterval:1 target:self selector:@selector(countAddWith:) userInfo:nil repeats:YES];
    return timer;
    }
    //3、使用中間類弱引用(weak)target
  • (NSTimer *)timerWithProxyTimerTimer{
    NSTimer *timer = [LFJProxyTimer weak_lfjScheduledTimerWithTimeInterval:1 target:self selector:@selector(countAddWith:) userInfo:nil repeats:YES];
    return timer;
    }

import "LFJWeakTimer.h"

@interface LFJWeakTimer()
@property (nonatomic,weak)id target;

@end
@implementation LFJWeakTimer

  • (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE(""){
    [anInvocation invokeWithTarget:self.target];
    }

  • (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""){
    //消息轉(zhuǎn)發(fā)(返回值methodSignature不能為空(調(diào)用這個(gè)方法的類沒有實(shí)現(xiàn)這個(gè)方法就會(huì)為空))
    return [self.target methodSignatureForSelector:aSelector];
    }

  • (void)dealloc{
    NSLog(@"weakTimer------釋放");
    }

  • (NSTimer *)weak_lfjScheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
    LFJWeakTimer weakTimer = [LFJWeakTimer new];
    weakTimer.target = aTarget;
    return [NSTimer scheduledTimerWithTimeInterval:ti target:weakTimer selector:aSelector userInfo:userInfo repeats:yesOrNo];
    //NSTimer 持有---->weakTimer 弱引用--->aTarget(當(dāng)NSTimer 調(diào)用invalidate的時(shí)候釋放weakTimer)
    //NSTimer 的target 都是強(qiáng)引用(即使使用__weak __typeof(&
    self)weakself = self 對weakself也是強(qiáng)引用讯屈,weakself也會(huì)和NSTimer引起循環(huán)引用)
    }

@end

@interface LFJProxyTimer : NSProxy

  • (instancetype)proxyWithObjc:(id)object;
  • (NSTimer *)weak_lfjScheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
    @end

import "LFJProxyTimer.h"

@interface LFJProxyTimer()
@property (nonatomic,weak)id target;

@end
@implementation LFJProxyTimer

  • (instancetype)initWithObjc:(id)object {

    self.target = object;
    return self;
    }
    //通過類方法創(chuàng)建創(chuàng)建

  • (instancetype)proxyWithObjc:(id)object{
    return [[self alloc] initWithObjc:object];

}

  • (void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:self.target];
    }

  • (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    //消息轉(zhuǎn)發(fā)(返回值methodSignature不能為空(調(diào)用這個(gè)方法的類沒有實(shí)現(xiàn)這個(gè)方法就會(huì)為空))
    return [self.target methodSignatureForSelector:aSelector];
    }

  • (void)dealloc{
    NSLog(@"weakTimer------釋放");
    }

  • (NSTimer *)weak_lfjScheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
    LFJProxyTimer proxyTimer = [LFJProxyTimer proxyWithObjc:aTarget];
    proxyTimer.target = aTarget;
    return [NSTimer scheduledTimerWithTimeInterval:ti target:proxyTimer selector:aSelector userInfo:userInfo repeats:yesOrNo];
    //NSTimer 持有---->weakTimer 弱引用--->aTarget(當(dāng)NSTimer 調(diào)用invalidate的時(shí)候釋放weakTimer)
    //NSTimer 的target 都是強(qiáng)引用(即使使用__weak __typeof(&
    self)weakself = self 對weakself也是強(qiáng)引用蛋哭,weakself也會(huì)和NSTimer引起循環(huán)引用)
    }
    @end
    方案四

//4、在當(dāng)前類釋放前釋放NSTimer(在ViewController執(zhí)行dealloc前釋放timer(不推薦))

  • (NSTimer *)timerWithNormal{
    objc_setAssociatedObject(self, &nameKey, @"1", OBJC_ASSOCIATION_ASSIGN);
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(countAddWith:) userInfo:@34 repeats:YES];
    return timer;
    }

  • (void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    NSString *dd = objc_getAssociatedObject(self, &nameKey);
    if (dd) {
    [self releaseTimer];
    }
    }

  • (void)dealloc{
    NSLog(@"釋放");
    [self releaseTimer];
    }

  • (void)restartTimer {
    [self startTimer];
    }

  • (void)releaseTimer{
    if (self.timer) {
    [self.timer invalidate];
    self.timer = nil;
    }
    }

作者:學(xué)不來的凡人
鏈接:http://www.reibang.com/p/fb52c60416ac
來源:簡書
著作權(quán)歸作者所有涮母。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)谆趾,非商業(yè)轉(zhuǎn)載請注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叛本,一起剝皮案震驚了整個(gè)濱河市沪蓬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌来候,老刑警劉巖跷叉,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡云挟,警方通過查閱死者的電腦和手機(jī)梆砸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來园欣,“玉大人帖世,你說我怎么就攤上這事】”樱” “怎么了狮暑?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長辉饱。 經(jīng)常有香客問我搬男,道長,這世上最難降的妖魔是什么彭沼? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任缔逛,我火速辦了婚禮,結(jié)果婚禮上姓惑,老公的妹妹穿的比我還像新娘褐奴。我一直安慰自己,他們只是感情好于毙,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布敦冬。 她就那樣靜靜地躺著,像睡著了一般唯沮。 火紅的嫁衣襯著肌膚如雪脖旱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天介蛉,我揣著相機(jī)與錄音萌庆,去河邊找鬼。 笑死币旧,一個(gè)胖子當(dāng)著我的面吹牛践险,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吹菱,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼巍虫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鳍刷?” 一聲冷哼從身側(cè)響起垫言,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎倾剿,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡前痘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年凛捏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芹缔。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坯癣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出最欠,到底是詐尸還是另有隱情示罗,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布芝硬,位于F島的核電站蚜点,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拌阴。R本人自食惡果不足惜绍绘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望迟赃。 院中可真熱鬧陪拘,春花似錦、人聲如沸纤壁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酌媒。三九已至欠痴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間馍佑,已是汗流浹背斋否。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拭荤,地道東北人茵臭。 一個(gè)月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像舅世,于是被迫代替她去往敵國和親旦委。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

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