NSTimer在IOS开发中会经常用到,尤其是小型游戏,然而对于初学者时常会注意不到其中的内存释放问题,将其基本用法总结如下:
一、初始化方法:有五种初始化方法,分别是
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
1
2
3
4
5
6
7
8
9
10
11
12
|
- (
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
1
2
|
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;
这是设置定时器的启动时间,常用来管理定时器的启动与停止
1
2
3
4
|
//启动定时器
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,都不能是这个界面释放,原因是系统的循环池中还保有这个对象。所以我们需要这样做:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
-(
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