關(guān)于NSTimer釋放和內(nèi)存泄漏的問題。
@(NSTimer)[內(nèi)存管理,NSTimer釋放,循環(huán)引用]
首先需要再次明確最基礎(chǔ)的iOS引用計數(shù)內(nèi)存管理模式(按照說人話的方式):
1)自己生成的對象谒获,自己所持有恶复。
2)非自己生成的對象,自己也能持有。
3)自己持有的對象不再需要時被釋放。
4)非自己持有的對象無法釋放。
坑出現(xiàn)的地方
美工要求在簽到按鈕上顯示一個時間的label雏吭,于是就封裝了一個專門用于顯示時間TimeLabel;
@interface FMTimeLabel ()
@property (nonatomic, weak) NSTimer *timer;
@end
@implementation FMTimeLabel
- (instancetype)init {
self = [super init];
if (self) {
//用NSTimer每秒循環(huán)執(zhí)行updateTime方法,達到更新label顯示內(nèi)容的目的煞额。
_timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(updateTime)
userInfo:nil
repeats:YES];
[self updateTime];
}
return self;
}
- (void) updateTime {
//每次執(zhí)行時思恐,獲取當(dāng)前時間的 時 分 秒 轉(zhuǎn)化成string類型給label顯示
NSString *time = [FMUtils getTimeDescriptionByDate:[NSDate date] format:@"hh:mm:ss"];
[self setText:time];
}
- (void)dealloc {
if (_timer) {
if ([_timer isValid]) {
[_timer invalidate];
_timer = nil;
}
}
}
@end
此時一切都很美好功能實現(xiàn)了效果也ok沾谜,但是多次刷新TableView之后,問題出現(xiàn)了胀莹。
timelabel直接卡死不再刷新時間基跑,并且也不走dealloc方法。
排查原因:內(nèi)存泄漏描焰,TimeLabel持有的NSTimer沒有被釋放媳否,導(dǎo)致TimeLabel也不能被釋放,從而導(dǎo)致線程掛起的狀態(tài)荆秦。
填坑的方法
問題分析:
原因是 Timer 添加到 Runloop 的時候篱竭,會被 Runloop 強引用,然后 Timer 又會有一個對 Target 的強引用(也就是 self )也就是說 NSTimer 強引用了 self 步绸,導(dǎo)致 self 一直不能被釋放掉掺逼,所以 self 的 dealloc 方法也一直未被執(zhí)行.
知道了錯誤原因,就先查一下NSTimer的官方文檔看看具體用法細節(jié)瓤介,發(fā)現(xiàn)NSTimer還有一個規(guī)則:(在哪個線程創(chuàng)建就要在哪個線程停止吕喘,否則會導(dǎo)致資源不能被正確的釋放。)那么問題來了:如果我就是想讓這個 NSTimer 一直輸出刑桑,直到 CustomerViewController 銷毀才停止并且釋放NSTimer氯质。
問題關(guān)鍵:
問題的關(guān)鍵就在于 self 被 NSTimer 強引用了,如果能打破這個強引用祠斧,問題應(yīng)該就能決了闻察。
問題解決:
(查閱到sunnyxx和TEASON有寫到過相關(guān)問題的原理及解決方案)我們可以造一個假的 target 給 NSTimer 。這個假的 target 類似于一個中間的代理人琢锋,它做的唯一的工作就是挺身而出接下了 NSTimer 的強引用辕漂。(這個解決方案甚是巧妙)然后在self釋放的時候隨self一起釋放,然后層層解扣吩蔑,達到在ViewController銷毀的時候釋放NSTimer钮热,這個target類聲明如下:(摘自TEASON)
@interface HWWeakTimerTarget : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer* timer;
@end
@implementation HWWeakTimerTarget
-(void) fire:(NSTimer *)timer {
if(self.target) {
[self.target performSelector:self.selector withObject:timer.userInfo];
} else {
[self.timer invalidate];
}
}
@end
然后再封裝一個假的NSTimer的方法 scheduledTimerWithTimeInterval 方法填抬,但是在調(diào)用的時候已經(jīng)偷梁換柱了:
+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats {
HWWeakTimerTarget* timerTarget = [[HWWeakTimerTarget alloc] init];
timerTarget.target = aTarget;
timerTarget.selector = aSelector;
timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:timerTarget
selector:@selector(fire:)
userInfo:userInfo
repeats:repeats];
return timerTarget.timer;
}
至此將原來NSTimer換成封裝好的CustomerTimer再次運行烛芬,問題解決。
FMTimer.h
#import <Foundation/Foundation.h>
typedef void (^FMTimerHandler)(id userInfo);
@interface FMWeakTimer : NSObject
+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(FMTimerHandler)block
userInfo:(id)userInfo
repeats:(BOOL)repeats;
@end
FMTimer.m
#import "FMWeakTimer.h"
@interface FMWeakTimerTarget : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer* timer;
@end
@implementation FMWeakTimerTarget
- (void) fire:(NSTimer *)timer {
if(self.target) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.target performSelector:self.selector withObject:timer.userInfo afterDelay:0.0f];
#pragma clang diagnostic pop
} else {
[self.timer invalidate];
}
}
@end
@implementation FMWeakTimer
+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats{
FMWeakTimerTarget* timerTarget = [[FMWeakTimerTarget alloc] init];
timerTarget.target = aTarget;
timerTarget.selector = aSelector;
timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:timerTarget
selector:@selector(fire:)
userInfo:userInfo
repeats:repeats];
return timerTarget.timer;
}
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(FMTimerHandler)block
userInfo:(id)userInfo
repeats:(BOOL)repeats {
NSMutableArray *userInfoArray = [NSMutableArray arrayWithObject:[block copy]];
if (userInfo != nil) {
[userInfoArray addObject:userInfo];
}
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(_timerBlockInvoke:)
userInfo:[userInfoArray copy]
repeats:repeats];
}
+ (void)_timerBlockInvoke:(NSArray*)userInfo {
FMTimerHandler block = userInfo[0];
id info = nil;
if (userInfo.count == 2) {
info = userInfo[1];
}
if (block) {
block(info);
}
}
@end
---玩的酷飒责,靠得住
僅做個人學(xué)習(xí)記錄所用赘娄,侵刪。