标签: objectvie-c 计时器
前言
在编写业务逻辑的时候,用到NSTimer的地方很多。比如,定期的更新窗口。对于它开发者文档中有如下描述:
You use the NSTimer class to create timer objects or, more simply, timers. A timer waits until a certain time interval has elapsed and then fires, sending a specified message to a target object. For example, you could create an NSTimer object that sends a message to a window, telling it to update itself after a certain time interval.
这是官方对NSTimer的介绍,就是用来在一个指定的时间间隔内,发送个一消息给一个指定的对象做指定你想要做的事。提到它就顺带提一下CFRunLoopTimerRef,这两个其实是一个东西,只不过是不同框架里的。前者是属于Foundation里的,属于ARC的管理范围,后者是属于Core Foundation里的,基于C语言实现的一个框架,不属于ARC的管理范围。但是这两个框架里的很多东西是是等价的,只是名字不同而已。
Timers work in conjunction with run loops.Note in particular that run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.A timer is not a real-time mechanism;it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed.
上面是节选的一段,个人认为比较重要的。主要是说NSTimer是和RunLoop一起工作的,而且RunLoop会对加入其中的NSTimer保持一个强引用。对NSTimer来说,一旦添加进RunLoop中,我们就不需要在外部去维护一个NSTimer的强引用。NSTimer的时间间隔也不是实时,可能比实际的时候多一点,也可能少一点,而且它只在指定的mode中运行。关于RunLoop,这是之前写的。
正文
以前用计时器的时候,只顾着用了,也没想太多,在后来的学习当中,发现计时器也会持有目标对象从而产生保留环,而且对重复执行的计时器,这种后果不仅是漏了内存还可能有更加严重。以下面的代码为例:
//Customer.h
#import <Foundation/Foundation.h>
@interface Customer : NSObject
@property(nonatomic, strong) NSTimer *timer;
- (void)start;
- (void)stop;
@end
//Customer.m
#import "MyTimer.h"
@implementation MyTimer
- (instancetype)init
{
return [super init];
}
- (void)stop
{
[self.timer invalidate];
self.timer = nil;
}
- (void)start
{
__weak MyTimer *timer = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(dosomething) userInfo:nil repeats:YES];
}
- (void)dosomething
{
NSLog(@"dosomething");
}
- (void)dealloc
{
[self.timer invalidate];
}
@end
//某个作用域
{
Customer *cus = [[Customer alloc] init];
[cus start];
}
如果一切正常情况,当出了作用域之后cus,cus便会被释放,计时器也会失效。但是因为计时器持有了self,而计时器作为属性又被self持有了,这样就出现了保留环,所以就算出了作用域cus其实也没有被释放即dealloc方法没有执行,计时器也没有失效,依然做着事,这样很可能会出现严重后果,这取决于所做的事。这里除非手动调用stop方法使计时器失效。那有没有更好的办法呢?答案是肯定的,那就是通过块来解决。可以为NSTimer增加一个分类:
//NSTimer+blockSupport.h
#import <Foundation/Foundation.h>
@interface NSTimer (blockSupport)
+ (NSTimer*)block_scheduledTimerWithTimerInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats;
@end
//NSTimer+blockSupport.m
#import "NSTimer+blockSupport.h"
@implementation NSTimer (blockSupport)
+ (NSTimer *)block_scheduledTimerWithTimerInterval:(NSTimeInterval)interval
block:(void (^)())block
repeats:(BOOL)repeats
{
return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(blockInvoke:) userInfo:[block copy] repeats:repeats];
}
+ (void)blockInvoke:(NSTimer *)timer
{
void (^block)() = timer.userInfo;
if (block) {
block();
}
}
@end
通过上面这个类方法创建的计时器,利用了userInfo参数将计时器要执行的任务封装成了块,该参数可以存放一个不透明值即万能值,只要计时器有效,该参数就会一直被保留。仅依靠这个分类还不行,还要对start方法修改如下:
- (void)start
{
__weak MyTimer *timer = self;
self.timer = [NSTimer block_scheduledTimerWithTimerInterval:1.0f block:^{
[timer dosomething];
} repeats:YES];
}
这样把计时器所做的事封装进了block,只要目标对象存在计时器就一直有效,而计时器捕获的只是self的一个弱引用,不会造成保留环。一旦目标对象被释放了,计时器立刻失效。以上内容参考了《Effectvie Objective-c》
–EOF–