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变量便可。

参考文档

相关文章
相关标签/搜索