參考鏈接:
NSTimer需要注意的地方
這是大神寫(xiě)的整個(gè)思路及解決辦法的文章
一.NSTimer和Run loop Modes
在Cocoa
中弛饭,每個(gè)線程(NSThread
)對(duì)象中內(nèi)部都有一個(gè)run loop
(NSRunLoop
)對(duì)象用來(lái)循環(huán)處理輸入事件(子線程默認(rèn)是沒(méi)創(chuàng)建的只有去獲取Runloop
的時(shí)候才創(chuàng)建)擂达。
處理的事件包括兩類,一是來(lái)自Input sources
的異步事件,一是來(lái)自Timer sources
的同步事件;run Loop
在處理輸入事件時(shí)會(huì)產(chǎn)生通知几于,可以通過(guò)Core Foundation
向線程中添加run-loop observers
來(lái)監(jiān)聽(tīng)特定事件,以在監(jiān)聽(tīng)的事件發(fā)生時(shí)做附加的處理工作话侧。
**Default mode(NSDefaultRunLoopMode)
**
//默認(rèn)模式中幾乎包含了所有輸入源(NSConnection除外),一般情況下應(yīng)使用此模式。
**Connection mode(NSConnectionReplyMode) **
//處理NSConnection對(duì)象相關(guān)事件益老,系統(tǒng)內(nèi)部使用彪蓬,用戶基本不會(huì)使用。
**Modal mode(NSModalPanelRunLoopMode) **
//處理modal panels事件捺萌。
**Event tracking mode(UITrackingRunLoopMode) **
//在拖動(dòng)loop或其他user interface tracking loops時(shí)處于此種模式下档冬,在此模式下會(huì)限制輸入事件的處理。例如桃纯,當(dāng)手指按住UITableView拖動(dòng)時(shí)就會(huì)處于此模式酷誓。
**Common mode(NSRunLoopCommonModes) **
//這是一個(gè)偽模式,其為一組run loop mode的集合态坦,將輸入源加入此模式意味著在Common Modes中包含的所有模式下都可以處理盐数。在Cocoa應(yīng)用程序中,默認(rèn)情況下Common Modes包含default modes,modal modes,event Tracking modes.可使用CFRunLoopAddCommonMode方法想Common Modes中添加自定義modes伞梯。
**注意: ** 所以 Timer
默認(rèn)是添加在 default mode
下的玫氢。當(dāng)我們拖動(dòng) UIScrollerView
的時(shí)候當(dāng)前的 runloop
是在 UITrackingRunLoopMode
帚屉。所以無(wú)法執(zhí)行timer
的對(duì)應(yīng)方法。
解決方法:
方法一:
在另外的線程中處理定時(shí)器事件琐旁,可把Timer加入到NSOperation中在另一個(gè)線程中調(diào)度;
方法二:
修改Timer運(yùn)行的run loop模式涮阔,將其加入到UITrackingRunLoopMode模式或NSRunLoopCommonModes模式中。
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(printMessage) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
二.NSTimer的生命周期
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(printMessage) userInfo:nil repeats:YES];
//Timer 添加到 Runloop 的時(shí)候灰殴,會(huì)被 Runloop 強(qiáng)引用敬特。
//Timer 又會(huì)有一個(gè)對(duì) Target 的強(qiáng)引用。
//所以說(shuō)如果不對(duì)Timer進(jìn)行釋放牺陶,Timer的targer(self)也一直不會(huì)被釋放伟阔。
//有時(shí)候我們我們對(duì)某個(gè)Timer的targer設(shè)置了nil。但沒(méi)設(shè)置[timer invalidate]掰伸。
//其實(shí)這個(gè)對(duì)象還是沒(méi)被釋放的皱炉。timer對(duì)應(yīng)的執(zhí)行方法也一直會(huì)在線程中執(zhí)行。容易造成內(nèi)存泄露狮鸭。
那么問(wèn)題來(lái)了:如果我就是想讓這個(gè) NSTimer 一直輸出合搅,直到 DemoViewController 銷毀了才停止,我該如何讓它停止呢歧蕉?
- NSTimer 被 Runloop 強(qiáng)引用了灾部,如果要釋放就要調(diào)用 invalidate 方法。
- 但是我想在 DemoViewController 的 dealloc 里調(diào)用 invalidate 方法惯退,但是 self 被 NSTimer 強(qiáng)引用了赌髓。
- 所以我還是要釋放 NSTimer 先,然而不調(diào)用 invalidate 方法就不能釋放它催跪。
- 然而你不進(jìn)入到 dealloc 方法里我又不能調(diào)用 invalidate 方法锁蠕。
** 注意:** NSTimer 在哪個(gè)線程創(chuàng)建就要在哪個(gè)線程停止,否則會(huì)導(dǎo)致資源不能被正確的釋放懊蒸∪偾悖看起來(lái)各種坑還不少。
方法一:
- weakSelf
問(wèn)題的關(guān)鍵就在于 self 被 NSTimer 強(qiáng)引用了榛鼎,如果我們能打破這個(gè)強(qiáng)引用問(wèn)題自然而然就解決了逃呼。所以一個(gè)很簡(jiǎn)單的想法就是:weakSelf:
__weak typeof(self) weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:3.0f
target:weakSelf
selector:@selector(timerFire:)
userInfo:nil
repeats:YES];
*然而這并沒(méi)有什么卵用,這里的 __weak
和 __strong
唯一的區(qū)別就是:如果在這兩行代碼執(zhí)行的期間self
被釋放了者娱, NSTimer
的target
會(huì)變成nil
。
- target
既然沒(méi)辦法通過(guò)__weak
把self
抽離出來(lái)苏揣,我們可以造個(gè)假的target
給NSTimer
黄鳍。這個(gè)假的target
類似于一個(gè)中間的代理人,它做的唯一的工作就是挺身而出接下了NSTimer
的強(qiáng)引用平匈。類聲明如下:*
@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
然后我們?cè)俜庋b個(gè)假的 scheduledTimerWithTimeInterval 方法
+(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;
}
方法二:
- block
如果能用 block 來(lái)調(diào)用 NSTimer 那豈不是更好了框沟。我們可以這樣來(lái)實(shí)現(xiàn):
+(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(HWTimerHandler)block
userInfo:(id)userInfo
repeats:(BOOL)repeats {
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(_timerBlockInvoke:)
userInfo:@[[block copy], userInfo]
repeats:repeats];
}
+(void)_timerBlockInvoke:(NSArray*)userInfo {
HWTimerHandler block = userInfo[0];
id info = userInfo[1];
// or `!block ?: block();` @sunnyxx
if (block) {
block(info);
}
}
這樣我們就可以直接在 block 里寫(xiě)相關(guān)邏輯了:
-(IBAction)fireButtonPressed:(id)sender {
_timer = [HWWeakTimer scheduledTimerWithTimeInterval:3.0f block:^(id userInfo) {
NSLog(@"%@", userInfo);
} userInfo:@"Fire" repeats:YES];
[_timer fire];
}