解決NSTimer和CADisplayLink強(qiáng)引用循環(huán)的幾種姿勢(shì)

NSTimerCADisplayLink使用不當(dāng)會(huì)造成強(qiáng)引用循環(huán)的問(wèn)題已經(jīng)是老生常談白嘁,但是一直也不放在心上(畢竟沒(méi)有給我踩到過(guò)坑)奋岁,最近在看ibreme大神的YYKit的過(guò)程中發(fā)現(xiàn)YYKit有專門來(lái)解決NSTimerCADisplayLink強(qiáng)引用循環(huán)的方法编振。自己寫了個(gè)Demo發(fā)現(xiàn)使用姿勢(shì)不當(dāng)?shù)脑挘瑥?qiáng)引用循環(huán)的坑還是很容易踩到的邓夕,下面總結(jié)下幾種解決這個(gè)問(wèn)題的姿勢(shì)萤厅。

問(wèn)題根源

先上一段日常我們的使用姿勢(shì)(本文以NSTimer為例,CADisplayLink同理):

Target.m
@property (nonatomic, strong) NSTimer *timer;
...
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(printNum:) userInfo:nil repeats:YES];
...
- (void)dealloc {
    [_timer invalidate];
}

在使用諸如:

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel

這些API時(shí)硕盹,NSTimer出于設(shè)計(jì)考慮會(huì)強(qiáng)引用target符匾,同時(shí)NSRunLoop又會(huì)持有持有NSTimer,使用方同時(shí)又持有了NSTimer瘩例,這就造成了強(qiáng)引用循環(huán):

Retain_Cycle.jpg

誤區(qū)

那就把NSTimer申明為weak吧 :)

Target.m
...
@property (nonatomic, weak) NSTimer *timer;
Retain_Cycle2.jpg

看起來(lái)沒(méi)問(wèn)題啊胶,但是Target的釋放依賴了NSTimer的釋放,然而NSTimer的釋放操作在Target的-delloc中垛贤,這是一個(gè)雞生蛋還是蛋生雞的問(wèn)題焰坪,依然會(huì)造成內(nèi)存泄露。

- (void)dealloc {
    [_timer invalidate];
}

那換個(gè)思路能不能讓NSTimer弱引用Target聘惦,我們?cè)诜乐笲lock的引用循環(huán)的措施中不是有這么一條么:

__typeof(&*self) __weak weakSelf = self;

那么能不能這么寫:

__typeof(&*self) __weak weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target: weakSelf selector:@selector(printNum:) userInfo:nil repeats:YES];

Naive,這樣子NSTimer仍然會(huì)強(qiáng)引用Target,NSTimer內(nèi)部不出意外是聲明一個(gè)__Strong的指針的某饰,不管你在這個(gè)接口中傳遞什么類型指針,最終都會(huì)持有。

自定義Category用Block解決

網(wǎng)上有一些封裝的比較好的block的解決方案黔漂,思路無(wú)外乎是封裝一個(gè)NSTimer的Category诫尽,提供block形式的接口:

NSTimer+AZ_Helper.h

+ (NSTimer *)AZ_scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)())inBlock repeats:(BOOL)inRepeats;
NSTimer+AZ_Helper.m

+ (NSTimer *)AZ_scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)())inBlock repeats:(BOOL)inRepeats
{
    void (^block)() = [inBlock copy];
    NSTimer * timer = [self scheduledTimerWithTimeInterval:inTimeInterval target:self selector:@selector(__executeTimerBlock:) userInfo:block repeats:inRepeats];
    return timer;
}

+ (void)__executeTimerBlock:(NSTimer *)inTimer;
{
    if([inTimer userInfo])
    {
        void (^block)() = (void (^)())[inTimer userInfo];
        block();
    }
}

使用者只需注意block中避免強(qiáng)引用循環(huán)就好了,這種方案已經(jīng)是比較簡(jiǎn)潔的解決方案炬守。但是也不是沒(méi)有缺點(diǎn)牧嫉,使用者不用使用原生的API了,同時(shí)要為NSTimer何CADisplayLink分別引進(jìn)一個(gè)Category减途。

GCD自己實(shí)現(xiàn)Timer

直接用GCD自己實(shí)現(xiàn)一個(gè)定時(shí)器酣藻,YYKit直接有一個(gè)現(xiàn)成的類YYTimer這里不再贅述。
缺點(diǎn):代價(jià)有點(diǎn)大鳍置,需要自己重新造一個(gè)定時(shí)器辽剧。

代理Proxy

NSProxy大家在日常開發(fā)中使用較少,其實(shí)就是一個(gè)代理中間人税产,可以代理其他的類/對(duì)象怕轿,用在這里很合適。先貼一張示意圖:

Weak_Proxy.jpg

完美解決強(qiáng)引用循環(huán)問(wèn)題砖第。下面是部分代碼:

WeakProxy.h
...
@property (nullable, nonatomic, weak, readonly) id target;
...
WeakProxy.m
...
- (instancetype)initWithTarget:(id)target {
    _target = target;
    return self;
}

+ (instancetype)proxyWithTarget:(id)target {
    return [[JYWeakProxy alloc] initWithTarget:target];
}

- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}
...

使用的時(shí)候只需要把self替換成[WeakProxy proxyWithTarget:self]

 _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[WeakProxy proxyWithTarget:self] selector:@selector(printNum:) userInfo:nil repeats:YES];

結(jié)語(yǔ)

筆者比較推薦WeakProxy的方式撤卢,代價(jià)很小,而且WeakProxy的功能只是一個(gè)純的代理梧兼,沒(méi)有和NSTimerCADisplayLink耦合,還可以用在其他地方智听;使用上對(duì)原有代碼的入侵也很小羽杰,原生的API依然正常使用。代碼放在了GitHub上到推。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末考赛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子莉测,更是在濱河造成了極大的恐慌颜骤,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捣卤,死亡現(xiàn)場(chǎng)離奇詭異忍抽,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)董朝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門鸠项,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人子姜,你說(shuō)我怎么就攤上這事祟绊。” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵牧抽,是天一觀的道長(zhǎng)嘉熊。 經(jīng)常有香客問(wèn)我,道長(zhǎng)扬舒,這世上最難降的妖魔是什么记舆? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮呼巴,結(jié)果婚禮上泽腮,老公的妹妹穿的比我還像新娘。我一直安慰自己衣赶,他們只是感情好诊赊,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著府瞄,像睡著了一般碧磅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上遵馆,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天鲸郊,我揣著相機(jī)與錄音,去河邊找鬼货邓。 笑死秆撮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的换况。 我是一名探鬼主播职辨,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼戈二!你這毒婦竟也來(lái)了舒裤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤觉吭,失蹤者是張志新(化名)和其女友劉穎腾供,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鲜滩,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伴鳖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绒北。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片黎侈。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖闷游,靈堂內(nèi)的尸體忽然破棺而出峻汉,到底是詐尸還是另有隱情贴汪,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布休吠,位于F島的核電站扳埂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瘤礁。R本人自食惡果不足惜阳懂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柜思。 院中可真熱鬧岩调,春花似錦、人聲如沸赡盘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)陨享。三九已至葱淳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抛姑,已是汗流浹背赞厕。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留定硝,地道東北人皿桑。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像喷斋,于是被迫代替她去往敵國(guó)和親唁毒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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