NSTimer 倒计时无法正常运行
NSTimer countdown not functioning correctly
我正在尝试区分时间并在 ui 标签中向用户显示倒数计时器
NSTimer
的声明
@property (strong, nonatomic)NSTimer *timer;
这是我看到的计时器已加载
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:@selector(updateCountdown:) userInfo:nil repeats: YES];
这是我的 updateCountdown 方法
-(void) updateCountdown:(int)secondsLeft {
int hours, minutes, seconds;
secondsLeft--;
hours = secondsLeft / 3600;
minutes = (secondsLeft % 3600) / 60;
seconds = (secondsLeft %3600) % 60;
_countDownlabel.text = [self timeFormatted:secondsLeft];///[NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds];
NSLog(@"%@",[NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds]);
if ( secondsLeft == 0 ) {
[_timer invalidate];
}
}
我的日志详细信息是
2017-06-30 09:49:34.070 Barebones[845:56963] requestReply cust liqour category: {
date = "30-6-2017";
"end_time" = "11:41";
"market_crash" = true;
"start_time" = "09:41";
}
2017-06-30 09:49:34.070 Barebones[845:56963] true
2017-06-30 09:49:34.070 Barebones[845:56963] 09:41
2017-06-30 09:49:34.070 Barebones[845:56963] 11:41
2017-06-30 09:49:34.070 Barebones[845:56963] 30-6-2017
2017-06-30 09:49:34.073 Barebones[845:56963] 2016-12-25 08:58:00 +0000
2017-06-30 09:49:34.073 Barebones[845:56963] 2016-12-25 12:15:00 +0000
2017-06-30 09:49:34.073 Barebones[845:56963] 197.000000 is the time difference
2017-06-30 09:49:34.073 Barebones[845:56963] 00:03:17
2017-06-30 09:49:35.075 Barebones[845:56963] 991:05:35
2017-06-30 09:49:36.075 Barebones[845:56963] 991:05:35
2017-06-30 09:49:37.075 Barebones[845:56963] 991:05:35
2017-06-30 09:49:38.075 Barebones[845:56963] 991:05:35
这个值继续执行
目标:- 倒计时到零并停止计时器实际上我会在倒计时结束后隐藏标签
更新:-
int secondsLeft=[self timeFormatted:[date2 timeIntervalSinceDate:date1]/60];
上面的定时器已经初始化
这是我更新的计时器:-
int secondsLeft=[date2 timeIntervalSinceDate:date1]/60;
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:secondsLeft], @"cID", nil];
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:@selector(updateCountdown:) userInfo:userInfo repeats: YES];
这是我更新的计时器方法:-
- (void)updateCountdown:(NSTimer *)timer{
int hours, minutes, seconds;
NSDictionary *userInfo = [timer userInfo];
int secondsLeft = [[userInfo objectForKey:@"cID"] intValue];
secondsLeft--;
hours = secondsLeft / 3600;
minutes = (secondsLeft % 3600) / 60;
seconds = (secondsLeft %3600) % 60;
_countDownlabel.text = [self timeFormatted:secondsLeft];///[NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds];
NSLog(@"%@",[NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds]);
if ( secondsLeft == 0 ) {
[_timer invalidate];
}
}
你的操作方法不可行。
如果传递的参数必须是NSTimer
实例
- (void)updateCountdown:(NSTimer *)timer
要传递自定义参数,请使用 userInfo
参数。
然而,更合适的解决方案是使用实例变量或 属性,因为 userInfo
的值必须是一个对象,例如 NSNumber
创建属性
@property (nonatomic) int secondsLeft;
在viewDidLoad
self.secondsLeft = 5 * 60; // 5 minutes
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:@selector(updateCountdown) userInfo:nil repeats: YES];
选择器:
-(void)updateCountdown{
int secondsLeft = self.secondsLeft;
if (secondsLeft >= 0) {
int minutes, seconds;
int hours;
self.secondsLeft--;
hours = secondsLeft / 3600;
minutes = (secondsLeft % 3600) / 60;
seconds = (secondsLeft %3600) % 60;
NSString *time = [NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds];
_countDownlabel.text = [self timeFormatted:secondsLeft];
}
if (secondsLeft <= 0) {
NSLog(@"TIME ENDS");
if ([self.timer isValid]) {
[self.timer invalidate];
self.timer = nil;
}
}
}
几件事。
正如 vadian 所说,如果您使用基于选择器的计时器,则计时器处理函数采用单个参数,该参数是对计时器本身的引用。如果你想跟踪倒计时,你可以定义属性来跟踪:
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UILabel *label;
@property (nonatomic, weak) NSTimer *timer;
@property (nonatomic, strong) NSDateComponentsFormatter *formatter;
@property (nonatomic, strong) NSDate *stopTime;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.formatter = [[NSDateComponentsFormatter alloc] init];
self.formatter.allowedUnits = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
self.formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
self.formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
self.stopTime = [[NSDate date] dateByAddingTimeInterval:5 * 60]; // in 5 minutes, for example
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:) userInfo:nil repeats:true];
[self.timer fire]; // don't wait one second before firing the first time; fire now
}
- (void)handleTimer:(NSTimer *)timer {
NSDate *now = [NSDate date];
if ([now compare:self.stopTime] == NSOrderedDescending) {
// do whatever you want when timer stops
[timer invalidate];
return;
}
self.label.text = [self.formatter stringFromDate:now toDate:self.stopTime];
}
// Note, when the view disappears, invalidate the timer so the timer doesn't
// keep strong reference to the view controller. Note that in this selector-based
// pattern, I can't attempt to do this in `dealloc`, because the scheduled timer
// will keep a strong reference, preventing `dealloc` from getting called. So do
// this in `viewDidDisappear`.
- (void)viewDidDisappear:(BOOL)animated {
[self.timer invalidate];
}
@end
如果仅支持 iOS 10 及更高版本,我建议使用完成块计时器,因为它进一步简化了过程:
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UILabel *label;
@property (nonatomic, weak) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init];
formatter.allowedUnits = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
NSDate *stopTime = [[NSDate date] dateByAddingTimeInterval:5 * 60]; // in 5 minutes, for example
typeof(self) __weak weakSelf = self; // make sure to not reference `self` in block below, but only reference `weakSelf` to avoid timer from maintaining strong reference
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:true block:^(NSTimer * _Nonnull timer) {
NSDate *now = [NSDate date];
if ([now compare:stopTime] == NSOrderedDescending) {
// do whatever you want when timer stops
[timer invalidate];
return;
}
weakSelf.label.text = [formatter stringFromDate:now toDate:stopTime];
}];
[self.timer fire]; // don't wait one second before firing the first time; fire now
}
// Because I was careful to not reference `self` inside the above block,
// this block-based timer will NOT keep a strong reference to the view
// controller. Nonetheless, when the view controller is dismissed, I want
// to stop the timer, to avoid wasting CPU cycles on a timer that isn't
// needed anymore.
- (void)dealloc {
[self.timer invalidate];
}
@end
显然,如果您也需要支持 iOS 9 和更早版本,那么您必须使用上述基于选择器的解决方案。
我建议不要依赖定时器来调整时间,因为你不能保证定时器会以你想要的频率被调用。在上面的两个例子中,我捕捉到我正在倒计时的时间,并且只显示 "now" 和预定的 "stop time".
之间的时间量
注意,我还建议您不要自己构建格式字符串。有一个方便的 NSDateComponentsFormatter
可以为您格式化。如果可以,请使用它。
您创建了计时器参考 strong
。您可以将其设置为 weak
,因为计划的计时器在 invalidated
之前不会被释放。一旦它是 invalidated
,自动为您解除分配就很方便了。
我正在尝试区分时间并在 ui 标签中向用户显示倒数计时器
NSTimer
@property (strong, nonatomic)NSTimer *timer;
这是我看到的计时器已加载
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:@selector(updateCountdown:) userInfo:nil repeats: YES];
这是我的 updateCountdown 方法
-(void) updateCountdown:(int)secondsLeft {
int hours, minutes, seconds;
secondsLeft--;
hours = secondsLeft / 3600;
minutes = (secondsLeft % 3600) / 60;
seconds = (secondsLeft %3600) % 60;
_countDownlabel.text = [self timeFormatted:secondsLeft];///[NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds];
NSLog(@"%@",[NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds]);
if ( secondsLeft == 0 ) {
[_timer invalidate];
}
}
我的日志详细信息是
2017-06-30 09:49:34.070 Barebones[845:56963] requestReply cust liqour category: {
date = "30-6-2017";
"end_time" = "11:41";
"market_crash" = true;
"start_time" = "09:41";
}
2017-06-30 09:49:34.070 Barebones[845:56963] true
2017-06-30 09:49:34.070 Barebones[845:56963] 09:41
2017-06-30 09:49:34.070 Barebones[845:56963] 11:41
2017-06-30 09:49:34.070 Barebones[845:56963] 30-6-2017
2017-06-30 09:49:34.073 Barebones[845:56963] 2016-12-25 08:58:00 +0000
2017-06-30 09:49:34.073 Barebones[845:56963] 2016-12-25 12:15:00 +0000
2017-06-30 09:49:34.073 Barebones[845:56963] 197.000000 is the time difference
2017-06-30 09:49:34.073 Barebones[845:56963] 00:03:17
2017-06-30 09:49:35.075 Barebones[845:56963] 991:05:35
2017-06-30 09:49:36.075 Barebones[845:56963] 991:05:35
2017-06-30 09:49:37.075 Barebones[845:56963] 991:05:35
2017-06-30 09:49:38.075 Barebones[845:56963] 991:05:35
这个值继续执行
目标:- 倒计时到零并停止计时器实际上我会在倒计时结束后隐藏标签
更新:-
int secondsLeft=[self timeFormatted:[date2 timeIntervalSinceDate:date1]/60];
上面的定时器已经初始化
这是我更新的计时器:-
int secondsLeft=[date2 timeIntervalSinceDate:date1]/60;
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:secondsLeft], @"cID", nil];
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:@selector(updateCountdown:) userInfo:userInfo repeats: YES];
这是我更新的计时器方法:-
- (void)updateCountdown:(NSTimer *)timer{
int hours, minutes, seconds;
NSDictionary *userInfo = [timer userInfo];
int secondsLeft = [[userInfo objectForKey:@"cID"] intValue];
secondsLeft--;
hours = secondsLeft / 3600;
minutes = (secondsLeft % 3600) / 60;
seconds = (secondsLeft %3600) % 60;
_countDownlabel.text = [self timeFormatted:secondsLeft];///[NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds];
NSLog(@"%@",[NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds]);
if ( secondsLeft == 0 ) {
[_timer invalidate];
}
}
你的操作方法不可行。
如果传递的参数必须是NSTimer
实例
- (void)updateCountdown:(NSTimer *)timer
要传递自定义参数,请使用 userInfo
参数。
然而,更合适的解决方案是使用实例变量或 属性,因为 userInfo
的值必须是一个对象,例如 NSNumber
创建属性
@property (nonatomic) int secondsLeft;
在viewDidLoad
self.secondsLeft = 5 * 60; // 5 minutes
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:@selector(updateCountdown) userInfo:nil repeats: YES];
选择器:
-(void)updateCountdown{
int secondsLeft = self.secondsLeft;
if (secondsLeft >= 0) {
int minutes, seconds;
int hours;
self.secondsLeft--;
hours = secondsLeft / 3600;
minutes = (secondsLeft % 3600) / 60;
seconds = (secondsLeft %3600) % 60;
NSString *time = [NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds];
_countDownlabel.text = [self timeFormatted:secondsLeft];
}
if (secondsLeft <= 0) {
NSLog(@"TIME ENDS");
if ([self.timer isValid]) {
[self.timer invalidate];
self.timer = nil;
}
}
}
几件事。
正如 vadian 所说,如果您使用基于选择器的计时器,则计时器处理函数采用单个参数,该参数是对计时器本身的引用。如果你想跟踪倒计时,你可以定义属性来跟踪:
@interface ViewController () @property (nonatomic, weak) IBOutlet UILabel *label; @property (nonatomic, weak) NSTimer *timer; @property (nonatomic, strong) NSDateComponentsFormatter *formatter; @property (nonatomic, strong) NSDate *stopTime; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.formatter = [[NSDateComponentsFormatter alloc] init]; self.formatter.allowedUnits = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; self.formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional; self.formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad; self.stopTime = [[NSDate date] dateByAddingTimeInterval:5 * 60]; // in 5 minutes, for example self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:) userInfo:nil repeats:true]; [self.timer fire]; // don't wait one second before firing the first time; fire now } - (void)handleTimer:(NSTimer *)timer { NSDate *now = [NSDate date]; if ([now compare:self.stopTime] == NSOrderedDescending) { // do whatever you want when timer stops [timer invalidate]; return; } self.label.text = [self.formatter stringFromDate:now toDate:self.stopTime]; } // Note, when the view disappears, invalidate the timer so the timer doesn't // keep strong reference to the view controller. Note that in this selector-based // pattern, I can't attempt to do this in `dealloc`, because the scheduled timer // will keep a strong reference, preventing `dealloc` from getting called. So do // this in `viewDidDisappear`. - (void)viewDidDisappear:(BOOL)animated { [self.timer invalidate]; } @end
如果仅支持 iOS 10 及更高版本,我建议使用完成块计时器,因为它进一步简化了过程:
@interface ViewController () @property (nonatomic, weak) IBOutlet UILabel *label; @property (nonatomic, weak) NSTimer *timer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init]; formatter.allowedUnits = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional; formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad; NSDate *stopTime = [[NSDate date] dateByAddingTimeInterval:5 * 60]; // in 5 minutes, for example typeof(self) __weak weakSelf = self; // make sure to not reference `self` in block below, but only reference `weakSelf` to avoid timer from maintaining strong reference self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:true block:^(NSTimer * _Nonnull timer) { NSDate *now = [NSDate date]; if ([now compare:stopTime] == NSOrderedDescending) { // do whatever you want when timer stops [timer invalidate]; return; } weakSelf.label.text = [formatter stringFromDate:now toDate:stopTime]; }]; [self.timer fire]; // don't wait one second before firing the first time; fire now } // Because I was careful to not reference `self` inside the above block, // this block-based timer will NOT keep a strong reference to the view // controller. Nonetheless, when the view controller is dismissed, I want // to stop the timer, to avoid wasting CPU cycles on a timer that isn't // needed anymore. - (void)dealloc { [self.timer invalidate]; } @end
显然,如果您也需要支持 iOS 9 和更早版本,那么您必须使用上述基于选择器的解决方案。
我建议不要依赖定时器来调整时间,因为你不能保证定时器会以你想要的频率被调用。在上面的两个例子中,我捕捉到我正在倒计时的时间,并且只显示 "now" 和预定的 "stop time".
之间的时间量
注意,我还建议您不要自己构建格式字符串。有一个方便的
NSDateComponentsFormatter
可以为您格式化。如果可以,请使用它。您创建了计时器参考
strong
。您可以将其设置为weak
,因为计划的计时器在invalidated
之前不会被释放。一旦它是invalidated
,自动为您解除分配就很方便了。