NSTimer在IOS开发中会经常用到,尤其是小型游戏,然而对于初学者时常会注意不到其中的内存释放问题,将其基本用法总结如下:
一、初始化方法:有五种初始化方法,分别是
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
123456789101112 | - ( void )viewDidLoad { [super viewDidLoad]; //初始化一个Invocation对象 NSInvocation * invo = [NSInvocation invocationWithMethodSignature:[[self class ] instanceMethodSignatureForSelector:@selector(init)]]; [invo setTarget:self]; [invo setSelector:@selector(myLog)]; NSTimer * timer = [NSTimer timerWithTimeInterval:1 invocation:invo repeats:YES]; //加入主循环池中 [[NSRunLoop mainRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode]; //开始循环 [timer fire]; } |
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
1 | NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1 invocation:invo repeats:YES]; |
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
1 | NSTimer * timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(myLog) userInfo:nil repeats:NO] |
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
1 | NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(myLog:) userInfo:@ "123" repeats:YES] |
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(id)ui repeats:(BOOL)rep
12 | NSTimer * timer = [[NSTimer alloc]initWithFireDate:[NSDate distantPast] interval:1 target:self selector:@selector(myLog:) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode]; |
注意:这五种初始化方法的异同:
1、参数repeats是指定是否循环执行,YES将循环,NO将只执行一次。
2、timerWithTimeInterval这两个类方法创建出来的对象如果不用 addTimer: forMode方法手动加入主循环池中,将不会循环执行。并且如果不手动调用fair,则定时器不会启动。
3、scheduledTimerWithTimeInterval这两个方法不需要手动调用fair,会自动执行,并且自动加入主循环池。
4、init方法需要手动加入循环池,它会在设定的启动时间启动。
二、成员变量
@property (copy) NSDate *fireDate;
这是设置定时器的启动时间,常用来管理定时器的启动与停止
1234 | //启动定时器 timer.fireDate = [NSDate distantPast]; //停止定时器 timer.fireDate = [NSDate distantFuture]; |
@property (readonly) NSTimeInterval timeInterval;
这个是一个只读属性,获取定时器调用间隔时间。
@property NSTimeInterval tolerance;
这是7.0之后新增的一个属性,因为NSTimer并不完全精准,通过这个值设置误差范围。
@property (readonly, getter=isValid) BOOL valid;
获取定时器是否有效
@property (readonly, retain) id userInfo;
获取参数信息
三、关于内存释放
如果我们启动了一个定时器,在某个界面释放前,将这个定时器停止,甚至置为nil,都不能是这个界面释放,原因是系统的循环池中还保有这个对象。所以我们需要这样做:
123456789101112131415161718 | -( void )dealloc{ NSLog(@ "dealloc:%@" ,[self class ]); } - ( void )viewDidLoad { [super viewDidLoad]; timer= [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(myLog:) userInfo:nil repeats:YES]; UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 100, 100)]; btn.backgroundColor=[UIColor redColor]; [btn addTarget:self action:@selector(btn) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:btn]; } -( void )btn{ if (timer.isValid) { [timer invalidate]; } timer=nil; [self dismissViewControllerAnimated:YES completion:nil]; } |
在官方文档中我们可以看到 [timer invalidate]是唯一的方法将定时器从循环池中移除。
OS程序进入后台后十分钟之内就会被系统kill掉,怎么解决呢?我想要程序进入后台后仍然运行计时功能,否则就无法达到考试的目的,之后在网上查阅了相关资料最后终于找到答案,其精髓就是:利用苹果给出的三种类型的程序可以保持在后台运行:音频播放类,位置更新类,另外一个记不太清楚了,我利用了苹果给出的音频播放类的这个“特权”来满足我程序上的要求,详细步骤如下:
1、步骤一:在Info.plist中,添加"Required background modes"键,value为:App plays audio
步骤二:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
NSError *setCategoryErr = nil;
NSError *activationErr = nil;
[[AVAudioSession sharedInstance]
setCategory: AVAudioSessionCategoryPlayback
error: &setCategoryErr];
[[AVAudioSession sharedInstance]
setActive: YES
error: &activationErr];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
步骤三:将以下代码添加到appDelegate文件中的- (void)applicationDidEnterBackground:(UIApplication *)application函数,也可添加到在具体类中注册的应用进入后台后的通知方法
- (void)applicationDidEnterBackground:(UIApplication *)application{
UIApplication* app = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask;
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
bgTask = UIBackgroundTaskInvalid;
}
});
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
bgTask = UIBackgroundTaskInvalid;
}
});
});}
问题解决之后遇到的一个新问题,我的页面上有一个UIScrollView和一个定时器用来记录当前考试模式下的剩余时间,问题出现了:当我滑动滚动试图时,定时器的方法便不在运行(即被UI主线程阻塞)。google一下找到了解决办法:将定时器放在非主线程中执行将更新UI的操作放到主线程,这样UI主线程和定时器就能互不干扰的相互工作了,以下是主要代码:
1 #import "CountdownTool.h"
2
3 @interface CountdownTool()
4 {
5 UILabel *_lblShow;
6 NSTimer *_timer;
7 }
8 @property (nonatomic, assign) NSInteger hour;
9 @property (nonatomic, assign) NSInteger minute;
10 @property (nonatomic, assign) NSInteger second;
11 @property (nonatomic, copy) NSString *strHour;
12 @property (nonatomic, copy) NSString *strMinute;
13 @property (nonatomic, copy) NSString *strSecond;
14 @property (nonatomic, assign) NSInteger totalSeconds;
15 @end
16 @implementation CountdownTool
17 @synthesize hour = _hour;
18 @synthesize minute = _minute;
19 @synthesize second = _second;
20 @synthesize totalSeconds = _totalSeconds;
21
22 - (void)dealloc
23 {
24 [_lblShow release];
25 [_strHour release];
26 [_strMinute release];
27 [_strSecond release];
28 [super dealloc];
29 }
30
31 - (id)initWithFrame:(CGRect)frame
32 {
33 self = [super initWithFrame:frame];
34 if (self) {
35 _lblShow = [[UILabel alloc] initWithFrame:self.bounds];
36 _lblShow.backgroundColor = [UIColor clearColor];
37 _lblShow.font = [UIFont systemFontOfSize:15];
38 _lblShow.textColor = [UIColor yellowColor];
39 _lblShow.textAlignment = NSTextAlignmentCenter;
40 _lblShow.numberOfLines = 1;
41 [self addSubview:_lblShow];
42 }
43 return self;
44 }
45
46 - (id)initWithFrame:(CGRect)frame andMinutesNum:(NSInteger)minute
47 {
48 if (self = [self initWithFrame:frame]) {
49 self.totalSeconds = minute * 60;
50 //多线程启动定时器
51 [NSThread detachNewThreadSelector:@selector(startTimer) toTarget:self withObject:nil];
52 }
53 return self;
54 }
55 - (void)startTimer
56 {
57 _timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(timerFire) userInfo:nil repeats:YES];
58 [[NSRunLoop currentRunLoop] run];
59 }
60 - (void)handleWithTotalSeconds
61 {
62 self.hour = _totalSeconds/3600;
63 self.minute = _totalSeconds%3600/60;
64 self.second = _totalSeconds%3600%60;
65 if (_hour <= 0) {
66 _lblShow.text = [NSString stringWithFormat:@"%@:%@",_strMinute,_strSecond];
67 }else{
68 _lblShow.text = [NSString stringWithFormat:@"%@:%@:%@",_strHour,_strMinute,_strSecond];
69 }
70 }
71 - (void)setHour:(NSInteger)hour
72 {
73 _hour = hour;
74 if (_hour < 10) {
75 self.strHour = [NSString stringWithFormat:@"0%d",_hour];
76 }else{
77 self.strHour = [NSString stringWithFormat:@"%d",_hour];
78 }
79 }
80 - (void)setMinute:(NSInteger)minute
81 {
82 _minute = minute;
83 if (_minute < 10) {
84 self.strMinute = [NSString stringWithFormat:@"0%d",_minute];
85 }else{
86 self.strMinute = [NSString stringWithFormat:@"%d",_minute];
87 }
88 }
89 - (void)setSecond:(NSInteger)second
90 {
91 _second = second;
92 if (_second < 10) {
93 self.strSecond = [NSString stringWithFormat:@"0%d",_second];
94 }else{
95 self.strSecond = [NSString stringWithFormat:@"%d",_second];
96 }
97 }
98 - (void)setTotalSeconds:(NSInteger)totalSeconds
99 {
100 _totalSeconds = totalSeconds;
101 [self performSelectorOnMainThread:@selector(handleWithTotalSeconds) withObject:nil waitUntilDone:YES];
102 }
103 - (void)timerFire
104 {
105 if (_totalSeconds == 0) {
106 [_timer invalidate];
107 return;
108 }
109 self.totalSeconds -= 1;
110 }
111 @end