NSTimer和實(shí)現(xiàn)弱引用的timer的方式

我們常用NSTimer的方式

如下代碼所示,是我們最常見的使用timer的方式

@property (nonatomic , strong) NSTimer *animationTimer;
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:(self.animationDuration = animationDuration)
                                                               target:self
                                                             selector:@selector(animationTimerDidFired:)
                                                             userInfo:nil
                                                              repeats:YES];

當(dāng)使用NSTimer的scheduledTimerWithTimeInterval方法時(shí)链患。事實(shí)上此時(shí)Timer會(huì)被加入到當(dāng)前線程的Run Loop中沪猴,且模式是默認(rèn)的NSDefaultRunLoopMode狠裹。而如果當(dāng)前線程就是主線程军援,也就是UI線程時(shí)挽唉,某些UI事件牛哺,比如UIScrollView的拖動(dòng)操作蟆豫,會(huì)將Run Loop切換成NSEventTrackingRunLoopMode模式,在這個(gè)過程中购岗,默認(rèn)的NSDefaultRunLoopMode模式中注冊(cè)的事件是不會(huì)被執(zhí)行的汰聋。也就是說(shuō)门粪,此時(shí)使用scheduledTimerWithTimeInterval添加到Run Loop中的Timer就不會(huì)執(zhí)行
我們可以通過添加一個(gè)UICollectionView,然后滑動(dòng)它后打印定時(shí)器方法
<pre>
2016-01-27 11:41:59.770 TimerAbout[89719:1419729] enter timer
2016-01-27 11:42:00.339 TimerAbout[89719:1419729] enter timer
2016-01-27 11:42:01.338 TimerAbout[89719:1419729] enter timer
2016-01-27 11:42:02.338 TimerAbout[89719:1419729] enter timer
2016-01-27 11:42:03.338 TimerAbout[89719:1419729] enter timer
2016-01-27 11:42:15.150 TimerAbout[89719:1419729] enter timer
2016-01-27 11:42:15.338 TimerAbout[89719:1419729] enter timer
</pre>

從中可以看到,當(dāng)UICollectionView滑動(dòng)時(shí)候,定時(shí)器方法并沒有打印(從03.338到15.150)

為了設(shè)置一個(gè)不被UI干擾的Timer喊积,我們需要手動(dòng)創(chuàng)建一個(gè)Timer,然后使用NSRunLoop的addTimer:forMode:方法來(lái)把Timer按照指定模式加入到Run Loop中玄妈。這里使用的模式是:NSRunLoopCommonModes乾吻,這個(gè)模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的結(jié)合,官方參考文檔

還是上面的例子,換為

self.animationTimer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(animationTimerDidFired:) userInfo:nil repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:self.animationTimer forMode:NSRunLoopCommonModes];

則,無(wú)論你滑動(dòng)不滑動(dòng)UICollectionView,定時(shí)器都是起作用的!!


上面的NSTimer無(wú)論采用何種方式,都是在主線程上跑的,那么怎么在非主線程中跑一個(gè)NSTimer呢?

我們簡(jiǎn)單的可以使用如下代碼

//創(chuàng)建并執(zhí)行新的線程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
    [thread start];

- (void)newThread
{
    @autoreleasepool
    {
        //在當(dāng)前Run Loop中添加timer,模式是默認(rèn)的NSDefaultRunLoopMode
        [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(animationTimerDidFired:) userInfo:nil repeats:YES];
        //開始執(zhí)行新線程的Run Loop
        [[NSRunLoop currentRunLoop] run];
    }
}

當(dāng)然了,因?yàn)槭情_啟的新的線程,在定時(shí)器的回調(diào)方法中,需要切換到主線程才能操作UI額

GCD的方式

//GCD方式
    uint64_t interval = 1 * NSEC_PER_SEC;
    //創(chuàng)建一個(gè)專門執(zhí)行timer回調(diào)的GCD隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("timerQueue", 0);
    _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //使用dispatch_source_set_timer函數(shù)設(shè)置timer參數(shù)
    dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
    //設(shè)置回調(diào)
    dispatch_source_set_event_handler(_timer, ^(){
        NSLog(@"Timer %@", [NSThread currentThread]);
    });
    dispatch_resume(_timer);//dispatch_source默認(rèn)是Suspended狀態(tài)拟蜻,通過dispatch_resume函數(shù)開始它

其中的dispatch_source_set_timer的最后一個(gè)參數(shù),是最后一個(gè)參數(shù)(leeway)绎签,他告訴系統(tǒng)我們需要計(jì)時(shí)器觸發(fā)的精準(zhǔn)程度。所有的計(jì)時(shí)器都不會(huì)保證100%精準(zhǔn)酝锅,這個(gè)參數(shù)用來(lái)告訴系統(tǒng)你希望系統(tǒng)保證精準(zhǔn)的努力程度诡必。如果你希望一個(gè)計(jì)時(shí)器每5秒觸發(fā)一次,并且越準(zhǔn)越好,那么你傳遞0為參數(shù)爸舒。另外蟋字,如果是一個(gè)周期性任務(wù),比如檢查email扭勉,那么你會(huì)希望每10分鐘檢查一次鹊奖,但是不用那么精準(zhǔn)。所以你可以傳入60涂炎,告訴系統(tǒng)60秒的誤差是可接受的忠聚。他的意義在于降低資源消耗。


一次性的timer方式的GCD模式

 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"dispatch_after enter timer");
    });

另一種dispatch_after方式的定時(shí)器

這個(gè)是使用上面的dispatch_after來(lái)創(chuàng)建的,通過遞歸調(diào)用來(lái)實(shí)現(xiàn)

- (void)dispatechAfterStyle {
    __weak typeof (self) wself = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"dispatch_after enter timer,thread = %@", [NSThread currentThread]);
        [wself dispatechAfterStyle];
    });
}

利用GCD的弱引用型的timer

MSWeaker 實(shí)現(xiàn)了一個(gè)利用GCD的弱引用的timer
原理是利用一個(gè)新的對(duì)象,在這個(gè)對(duì)象中

NSString *privateQueueName = [NSString stringWithFormat:@"com.mindsnacks.msweaktimer.%p", self];
        self.privateSerialQueue = dispatch_queue_create([privateQueueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
        dispatch_set_target_queue(self.privateSerialQueue, dispatchQueue);

        self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
                                            0,
                                            0,
                                            self.privateSerialQueue);

- (void)resetTimerProperties
{
    int64_t intervalInNanoseconds = (int64_t)(self.timeInterval * NSEC_PER_SEC);
    int64_t toleranceInNanoseconds = (int64_t)(self.tolerance * NSEC_PER_SEC);

    dispatch_source_set_timer(self.timer,
                              dispatch_time(DISPATCH_TIME_NOW, intervalInNanoseconds),
                              (uint64_t)intervalInNanoseconds,
                              toleranceInNanoseconds
                              );
}

- (void)schedule
{
    [self resetTimerProperties];

    __weak MSWeakTimer *weakSelf = self;

    dispatch_source_set_event_handler(self.timer, ^{
        [weakSelf timerFired];
    });

    dispatch_resume(self.timer);
}

創(chuàng)建了一個(gè)隊(duì)列self.timer = dispatch_source_create,然后在這個(gè)隊(duì)列中創(chuàng)建timer dispatch_source_set_timer
注意其中用到了dispatch_set_target_queue(self.privateSerialQueue, dispatchQueue); 這個(gè)是將dispatch隊(duì)列的執(zhí)行操作放到隊(duì)列dispatchQueue 中去

這份代碼中還用到了原子操作!!!值得好好研讀,以便以后可以在自己的多線程設(shè)計(jì)中使用原子操作
為什么用原子操作呢,因?yàn)樽髡呦氲氖窃诙嗑€程的環(huán)境下設(shè)置定時(shí)器的開關(guān)與否

if (OSAtomicAnd32OrigBarrier(1, &_timerFlags.timerIsInvalidated))

if (!OSAtomicTestAndSet(7, &_timerFlags.timerIsInvalidated))
    {
        dispatch_source_t timer = self.timer;
        dispatch_async(self.privateSerialQueue, ^{
            dispatch_source_cancel(timer);
            ms_release_gcd_object(timer);
        });
    }

至于其中

 struct
    {
        uint32_t timerIsInvalidated;
    } _timerFlags;   

這里為什么要用結(jié)構(gòu)體呢?為什么不直接使用一個(gè)uint32_t 的變量???


使用NSTimer方式創(chuàng)建的Timer,使用時(shí)候需要注意

由于

self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:1
                                                           target:self
                                                         selector:@selector(animationTimerDidFired:)
                                                         userInfo:nil
                                                          repeats:YES];

會(huì)導(dǎo)致timer 強(qiáng)引用 self,而animationTimer又是self的一個(gè)強(qiáng)引用,這造成了強(qiáng)引用的循環(huán)了
如果不手工停止timer,那么self這個(gè)VC將不能夠被釋放,尤其是當(dāng)我們這個(gè)VC是push進(jìn)來(lái)的時(shí)候,pop將不會(huì)被釋放!!!
怎么解決呢??
當(dāng)然了,可以采用上文提到的MSWeakerGCD的弱引用的timer

可是如果有時(shí)候,我們不想使用它,覺得它有點(diǎn)復(fù)雜呢?

  1. 在VC的disappear方法中應(yīng)該調(diào)用 invalidate方法,將定時(shí)器釋放掉,這里可能有人要說(shuō)了,我直接在vc的dealloc中釋放不行么

-(void)dealloc {
[_animationTimer invalidate];
}
```
很遺憾的告訴你,都已經(jīng)循環(huán)引用了,vc壓根就釋放不了,怎么調(diào)dealloc方法!!

  1. 在vc的disappear方法中

-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[_animationTimer invalidate];
}
```
這樣的確能解決問題,可是不一定是我們想要的呀,當(dāng)我們vc 再push了一個(gè)新的頁(yè)面的時(shí)候,本身vc沒有釋放,按理說(shuō),其成員timer不應(yīng)該被釋放呀,你可能會(huì)說(shuō),那還不容易,在appear方法中再重新生成一下唄....但是這樣的話,又要增加一個(gè)變量,標(biāo)識(shí)定時(shí)器在上一次disappear時(shí)候是不是啟動(dòng)了吧,是啟動(dòng)了,被invaliate的時(shí)候,才能在appear中重新啟動(dòng)吧.這樣,是不是覺得很麻煩!!

  1. 你可能會(huì)說(shuō),那簡(jiǎn)單啊,直接若引用就可以了想想我們使用block的時(shí)候

@property (nonatomic, copy) void (^ myblock)(NSInteger i);
__weak typeof (self) weakSelf = self;
self.myblock = ^(NSInteger i){
[weakSelf view];
};
```
在其中,我們需要在block中引用self,如果直接引用,也是循環(huán)引用了,采用先定義一個(gè)weak變量,然后在block中引用weak對(duì)象,避免循環(huán)引用 你會(huì)直接想到如下的方式

```Objective-C

__weak typeof (self) wself = self;
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:1
target:wself
selector:@selector(animationTimerDidFired:)
userInfo:nil
repeats:YES];
是不是瞬間覺得完美了,呵呵,我只能說(shuō)少年,你沒理解兩者之間的區(qū)別.在block中,block是對(duì)變量進(jìn)行捕獲,意思是對(duì)使用到的**變量**進(jìn)行拷貝操作,注意是拷貝的不是對(duì)象,而是變量自身,拿上面的來(lái)說(shuō),block中只是對(duì)變量wself拷貝了一份,也就是說(shuō),block中也定義了一個(gè)weak對(duì)象,相當(dāng)于,在block的內(nèi)存區(qū)域中,定義了一個(gè)__weak blockWeak對(duì)象,然后執(zhí)行了blockWeak = wself;注意到了沒,這里并沒有引起對(duì)象的持有量的變化,所以沒有問題,再看timer的方式,雖然你是將wself傳入了timer的構(gòu)造方法中,我們可以查看NSTimer的Objective-C

  • (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

定義,其target的說(shuō)明The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated,是要強(qiáng)應(yīng)用這個(gè)變量的 也就是說(shuō),大概是這樣的, __strong strongSelf = wself 強(qiáng)引用了一個(gè)弱應(yīng)用的變量,結(jié)果還是強(qiáng)引用,也就是說(shuō)strongSelf持有了wself所指向的對(duì)象(也即是self所只有的對(duì)象),這和你直接傳self進(jìn)來(lái)是一樣的效果,并不能達(dá)到解除強(qiáng)引用的作用!! 看來(lái)只能換個(gè)思路了,我直接生成一個(gè)臨時(shí)對(duì)象,讓Timer強(qiáng)用用這個(gè)臨時(shí)對(duì)象,在這個(gè)臨時(shí)對(duì)象中弱引用self,可以了吧.

  1. 考慮引入一個(gè)對(duì)象,在這個(gè)對(duì)象中弱引用self,然后將這個(gè)對(duì)象傳遞給timer的構(gòu)建方法 這里可以參考YYWeakProxy建立這個(gè)對(duì)象
@interface YYWeakProxy : NSProxy
@property (nonatomic, weak, readonly) id target;
-(instancetype)initWithTarget:(id)target;
+(instancetype)proxyWithTarget:(id)target;
@end
-(instancetype)initWithTarget:(id)target {
  _target = target;
  return self;
}
+(instancetype)proxyWithTarget:(id)target {
  return [[YYWeakProxy alloc] initWithTarget:target];
}
//當(dāng)不能識(shí)別方法時(shí)候,就會(huì)調(diào)用這個(gè)方法,在這個(gè)方法中,我們可以將不能識(shí)別的傳遞給其它對(duì)象處理
//由于這里對(duì)所有的不能處理的都傳遞給_target了,所以methodSignatureForSelector和forwardInvocation不可能被執(zhí)行的,所以不用再重載了吧
//其實(shí)還是需要重載methodSignatureForSelector和forwardInvocation的,為什么呢?因?yàn)開target是弱引用的,所以當(dāng)_target可能釋放了,當(dāng)它被釋放了的情況下,那么forwardingTargetForSelector就是返回nil了.然后methodSignatureForSelector和forwardInvocation沒實(shí)現(xiàn)的話,就直接crash了!!!
//這也是為什么這兩個(gè)方法中隨便寫的!!!
-(id)forwardingTargetForSelector:(SEL)selector {
  return _target;
}
-(void)forwardInvocation:(NSInvocation *)invocation {
  void *null = NULL;
  [invocation setReturnValue:&null];
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
  return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
-(BOOL)respondsToSelector:(SEL)aSelector {
  return [_target respondsToSelector:aSelector];
}
-(BOOL)isEqual:(id)object {
  return [_target isEqual:object];
}
-(NSUInteger)hash {
  return [_target hash];
}
-(Class)superclass {
  return [_target superclass];
}
-(Class)class {
  return [_target class];
}
-(BOOL)isKindOfClass:(Class)aClass {
  return [_target isKindOfClass:aClass];
}
-(BOOL)isMemberOfClass:(Class)aClass {
  return [_target isMemberOfClass:aClass];
}
-(BOOL)conformsToProtocol:(Protocol *)aProtocol {
  return [_target conformsToProtocol:aProtocol];
}
-(BOOL)isProxy {
  return YES;
}
-(NSString *)description {
  return [_target description];
}
-(NSString *)debugDescription {
  return [_target debugDescription];
}
@end

使用的時(shí)候,將原來(lái)的替換為

```Objective-C

self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:1
target:[YYWeakProxy proxyWithTarget:self ]
selector:@selector(animationTimerDidFired:)
userInfo:nil
repeats:YES];
```

  1. block方式來(lái)解決循環(huán)引用
@interface NSTimer (XXBlocksSupport)
+(NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                       block:(void(^)())block
                                     repeats:(BOOL)repeats;
@end
@implementation NSTimer (XXBlocksSupport)
+(NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                       block:(void(^)())block
                                     repeats:(BOOL)repeats
{
  return [self scheduledTimerWithTimeInterval:interval
                                        target:self
                                      selector:@selector(xx_blockInvoke:)
                                      userInfo:[block copy]
                                       repeats:repeats];
}
+(void)xx_blockInvoke:(NSTimer *)timer {
  void (^block)() = timer.userinfo;
  if(block) {
      block();
  }
}
@end
  ```
注意以上NSTimer的target是NSTimer類對(duì)象,類對(duì)象本身是個(gè)單利,此處雖然也是循環(huán)引用,但是由于類對(duì)象不需要回收,所以沒有問題.但是這種方式要注意block的間接循環(huán)引用,當(dāng)然了,解決block的間接循環(huán)引用很簡(jiǎn)單,定義一個(gè)weak變量,在block中使用weak變量即可




---

###參考文檔
1.  [NSTimer循環(huán)引用不釋放問題](http://www.codecate.com/code/archives/77)
2. [使用block解決NSTimer循環(huán)引用](http://www.reibang.com/p/1dbd7a228a22 )
3. [weak NSTimer](http://blog.csdn.net/majiakun1/article/details/43700773)
4. [弱引用NSTimer對(duì)象](http://blog.csdn.net/tangaowen/article/details/46823199)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末唱捣,一起剝皮案震驚了整個(gè)濱河市两蟀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌震缭,老刑警劉巖垫竞,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蛀序,居然都是意外死亡欢瞪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門徐裸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)遣鼓,“玉大人,你說(shuō)我怎么就攤上這事重贺∑锼睿” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵气笙,是天一觀的道長(zhǎng)次企。 經(jīng)常有香客問我,道長(zhǎng)潜圃,這世上最難降的妖魔是什么缸棵? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮谭期,結(jié)果婚禮上堵第,老公的妹妹穿的比我還像新娘。我一直安慰自己隧出,他們只是感情好踏志,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著胀瞪,像睡著了一般针余。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天圆雁,我揣著相機(jī)與錄音傍妒,去河邊找鬼。 笑死摸柄,一個(gè)胖子當(dāng)著我的面吹牛颤练,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播驱负,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼嗦玖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了跃脊?” 一聲冷哼從身側(cè)響起宇挫,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎酪术,沒想到半個(gè)月后器瘪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绘雁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年橡疼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庐舟。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡欣除,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挪略,到底是詐尸還是另有隱情历帚,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布杠娱,位于F島的核電站挽牢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏摊求。R本人自食惡果不足惜禽拔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望睹簇。 院中可真熱鬧奏赘,春花似錦寥闪、人聲如沸太惠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)凿渊。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間埃脏,已是汗流浹背搪锣。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留彩掐,地道東北人构舟。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像堵幽,于是被迫代替她去往敵國(guó)和親狗超。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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