NSTimer和實現弱引用的timer的方式

本文是投稿文章,做者:yohunl html


目錄ios

  • 咱們經常使用NSTimer的方式git

  • 上面的NSTimer不管採用何種方式都是在主線程上跑的那麼怎麼在非主線程中跑一個NSTimer呢github

  • GCD的方式多線程

  • 一次性的timer方式的GCD模式app

  • 另外一種dispatch_after方式的定時器async

  • 利用GCD的弱引用型的timer函數

  • 使用NSTimer方式建立的Timer使用時候須要注意oop

  • 參考文檔ui

  • 咱們經常使用NSTimer的方式

以下代碼所示,是咱們最多見的使用timer的方式

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

當使用NSTimer的scheduledTimerWithTimeInterval方法時。事實上此時Timer會被加入到當前線程的Run Loop中,且模式是默認的NSDefaultRunLoopMode。而若是當前線程就是主線程,也就是UI線程時,某些UI事件,好比UIScrollView的拖動操做,會將Run Loop切換成NSEventTrackingRunLoopMode模式,在這個過程當中,默認的NSDefaultRunLoopMode模式中註冊的事件是不會被執行的。也就是說,此時使用scheduledTimerWithTimeInterval添加到Run Loop中的Timer就不會執行。
咱們能夠經過添加一個UICollectionView,而後滑動它後打印定時器方法

1
2
3
4
5
6
7
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

從中能夠看到,當UICollectionView滑動時候,定時器方法並無打印(從03.338到15.150)

爲了設置一個不被UI干擾的Timer,咱們須要手動建立一個Timer,而後使用NSRunLoop的addTimer:forMode:方法來把Timer按照指定模式加入到Run Loop中。這裏使用的模式是:NSRunLoopCommonModes,這個模式等效於NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的結合,官方參考文檔

仍是上面的例子,換爲:

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

則,不管你滑動不滑動UICollectionView,定時器都是起做用的!

上面的NSTimer不管採用何種方式,都是在主線程上跑的,那麼怎麼在非主線程中跑一個NSTimer呢?

咱們簡單的可使用以下代碼:

1
2
3
4
5
6
7
8
9
10
11
12
//建立並執行新的線程
     NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
     [thread start];
     
- (void)newThread
{
     @autoreleasepool
     {         //在當前Run Loop中添加timer,模式是默認的NSDefaultRunLoopMode
         [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(animationTimerDidFired:) userInfo:nil repeats:YES];         //開始執行新線程的Run Loop
         [[NSRunLoop currentRunLoop] run];
     }
}1234567891011121314

固然了,由於是開啓的新的線程,在定時器的回調方法中,須要切換到主線程才能操做UI。

GCD的方式

1
2
3
4
5
6
7
8
9
10
11
12
//GCD方式
     uint64_t interval = 1 * NSEC_PER_SEC;
     //建立一個專門執行timer回調的GCD隊列
     dispatch_queue_t queue = dispatch_queue_create( "timerQueue" , 0);
     _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
     //使用dispatch_source_set_timer函數設置timer參數
     dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
     //設置回調
     dispatch_source_set_event_handler(_timer, ^(){
         NSLog(@ "Timer %@" , [NSThread currentThread]);
     });
     dispatch_resume(_timer); //dispatch_source默認是Suspended狀態,經過dispatch_resume函數開始它123456789101112

其中的dispatch_source_set_timer的最後一個參數,是最後一個參數(leeway),它告訴系統咱們須要計時器觸發的精準程度。全部的計時器都不會保證100%精準,這個參數用來告訴系統你但願系統保證精準的努力程度。若是你但願一個計時器每5秒觸發一次,而且越準越好,那麼你傳遞0爲參數。另外,若是是一個週期性任務,好比檢查email,那麼你會但願每10分鐘檢查一次,可是不用那麼精準。因此你能夠傳入60,告訴系統60秒的偏差是可接受的。他的意義在於下降資源消耗。

一次性的timer方式的GCD模式

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

另外一種dispatch_after方式的定時器

這個是使用上面的dispatch_after來建立的,經過遞歸調用來實現。

1
2
3
4
5
6
7
- (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實現了一個利用GCD的弱引用的timer。原理是利用一個新的對象,在這個對象中:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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);
}

建立了一個隊列self.timer = dispatch_source_create,而後在這個隊列中建立timer dispatch_source_set_timer.

注意其中用到了dispatch_set_target_queue(self.privateSerialQueue, dispatchQueue); 這個是將dispatch隊列的執行操做放到隊列dispatchQueue 中去。

這份代碼中還用到了原子操做!值得好好研讀,以便之後能夠在本身的多線程設計中使用原子操做。
爲何用原子操做呢,由於做者想的是在多線程的環境下設置定時器的開關與否。

 

1
2
3
4
5
6
7
8
9
10
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);
         });
     }

至於其中

1
2
3
4
  struct
     {
         uint32_t timerIsInvalidated;
     } _timerFlags;

這裏爲何要用結構體呢?爲何不直接使用一個uint32_t 的變量?

使用NSTimer方式建立的Timer,使用時候須要注意。

因爲

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

會致使timer 強引用 self,而animationTimer又是self的一個強引用,這形成了強引用的循環了。 
若是不手工中止timer,那麼self這個VC將不可以被釋放,尤爲是當咱們這個VC是push進來的時候,pop將不會被釋放!!! 
怎麼解決呢?
固然了,能夠採用上文提到的MSWeakerGCD的弱引用的timer

但是若是有時候,咱們不想使用它,以爲它有點複雜呢?

1.在VC的disappear方法中應該調用 invalidate方法,將定時器釋放掉,這裏可能有人要說了,我直接在vc的dealloc中釋放不行麼?

1
2
3
-(void)dealloc {
[_animationTimer invalidate];
}

很遺憾的告訴你,都已經循環引用了,vc壓根就釋放不了,怎麼調dealloc方法?

在vc的disappear方法中

1
2
3
4
-(void)viewWillDisappear:(BOOL)animated {
[ super  viewWillDisappear:animated];
[_animationTimer invalidate];
}

這樣的確能解決問題,但是不必定是咱們想要的呀,當咱們vc 再push了一個新的頁面的時候,自己vc沒有釋放,按理說,其成員timer不該該被釋放呀,你可能會說,那還不容易,在appear方法中再從新生成一下唄…可是這樣的話,又要增長一個變量,標識定時器在上一次disappear時候是否是啓動了吧,是啓動了,被invaliate的時候,才能在appear中從新啓動吧。這樣是否是以爲很麻煩?

3.你可能會說,那簡單啊,直接若引用就能夠了想一想咱們使用block的時候

1
2
3
4
5
@property (nonatomic, copy) void  (^ myblock)(NSInteger i);
__weak  typeof  (self) weakSelf = self;
self.myblock = ^(NSInteger i){
     [weakSelf view];
};

在其中,咱們須要在block中引用self,若是直接引用,也是循環引用了,採用先定義一個weak變量,而後在block中引用weak對象,避免循環引用 你會直接想到以下的方式

1
2
3
4
5
6
__weak  typeof  (self) wself = self;
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:1
                                                        target:wself
                                                      selector:@selector(animationTimerDidFired:)
                                                      userInfo:nil
                                                       repeats:YES];

是否是瞬間以爲完美了,呵呵,我只能說少年,你沒理解二者之間的區別。在block中,block是對變量進行捕獲,意思是對使用到的變量進行拷貝操做,注意是拷貝的不是對象,而是變量自身。拿上面的來講,block中只是對變量wself拷貝了一份,也就是說,block中也定義了一個weak對象,至關於,在block的內存區域中,定義了一個__weak blockWeak對象,而後執行了blockWeak = wself;注意到了沒,這裏並無引發對象的持有量的變化,因此沒有問題,再看timer的方式,雖然你是將wself傳入了timer的構造方法中,咱們能夠查看NSTimer的

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

定義,其target的說明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,是要強應用這個變量的 也就是說,大概是這樣的,__strong strongSelf = wself 強引用了一個弱應用的變量,結果仍是強引用,也就是說strongSelf持有了wself所指向的對象(也便是self所只有的對象),這和你直接傳self進來是同樣的效果,並不能達到解除強引用的做用!看來只能換個思路了,我直接生成一個臨時對象,讓Timer強用用這個臨時對象,在這個臨時對象中弱引用self,能夠了吧。

4.考慮引入一個對象,在這個對象中弱引用self,而後將這個對象傳遞給timer的構建方法 這裏能夠參考YYWeakProxy創建這個對象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@interface YYWeakProxy : NSProxy
@property (nonatomic, weak, readonly) id target;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation YYWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return  self;
}
+ (instancetype)proxyWithTarget:(id)target {
return  [[YYWeakProxy alloc] initWithTarget:target];
}
//當不能識別方法時候,就會調用這個方法,在這個方法中,咱們能夠將不能識別的傳遞給其它對象處理
//因爲這裏對全部的不能處理的都傳遞給_target了,因此methodSignatureForSelector和forwardInvocation不可能被執行的,因此不用再重載了吧
//其實仍是須要重載methodSignatureForSelector和forwardInvocation的,爲何呢?由於_target是弱引用的,因此當_target可能釋放了,當它被釋放了的狀況下,那麼forwardingTargetForSelector就是返回nil了.而後methodSignatureForSelector和forwardInvocation沒實現的話,就直接crash了!!!
//這也是爲何這兩個方法中隨便寫的!!!
- (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

使用的時候,將原來的替換爲:

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

5.block方式來解決循環引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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類對象,類對象自己是個單利,此處雖然也是循環引用,可是因爲類對象不須要回收,因此沒有問題。可是這種方式要注意block的間接循環引用,固然了,解決block的間接循環引用很簡單,定義一個weak變量,在block中使用weak變量便可。

參考文檔

相關文章
相關標籤/搜索