NSTimer的运行机制
NSTimer 是一个用于在 iOS 和 macOS 应用中定时执行任务的类。它的运行机制主要依赖于 run loop。下面详细解释 NSTimer 的运行机制:
NSTimer 的基本使用
创建一个 NSTimer 通常使用以下方法之一:
-
计划重复计时器:
objective-c[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; -
手动管理计时器:
objective-cNSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
运行机制
-
创建计时器:
当你创建一个
NSTimer实例时,它会被初始化,但不会立即开始运行。创建时指定的时间间隔timeInterval决定了计时器触发的频率。 -
添加到 Run Loop:
计时器必须被添加到一个
NSRunLoop中才能开始运行。可以手动将计时器添加到 run loop 中,或者使用scheduledTimer方法自动将其添加到当前 run loop 中。 -
Run Loop 监控:
Run loop 会不断地监听其所管理的所有事件源,包括
NSTimer。当计时器的时间间隔到达时,run loop 会检测到这个事件,并触发相应的处理方法。 -
触发处理方法:
当计时器触发时,指定的目标对象的选择器方法会被调用。在上面的例子中,当计时器触发时,
timerFired:方法会被执行。 -
重复计时器:
如果计时器被设置为重复 (
repeats:YES),它将在每次触发后自动重置并继续运行,直到被手动无效化 (invalidate)。 -
停止计时器:
当你调用
invalidate方法时,计时器会从 run loop 中移除并停止运行。计时器对象将不再有效并且无法再次使用。
注意事项
-
Run Loop 模式:
计时器会运行在特定的 run loop 模式下。默认情况下,它们运行在
NSDefaultRunLoopMode下。这意味着当 run loop 进入不同的模式(例如拖拽模式时),计时器可能不会触发。因此,有时需要将计时器添加到NSRunLoopCommonModes中,以确保它在多种模式下都能触发。 -
准确性:
NSTimer的触发时间并不是精确的。它受到 run loop 负载和其他系统事件的影响,可能会有一些延迟。如果需要更高的精确性,可能需要考虑使用CADisplayLink或dispatch_source_t计时器。
示例代码
objective-c
- (void)startTimer {
// 创建一个每秒触发一次的计时器
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerFired:)
userInfo:nil
repeats:YES];
}
- (void)timerFired:(NSTimer *)timer {
// 计时器触发时执行的代码
NSLog(@"Timer fired!");
}
- (void)stopTimer {
// 停止计时器
[self.timer invalidate];
self.timer = nil;
}
总结
NSTimer 是一个强大的工具,用于在应用中定时执行任务。它依赖于 run loop 运行,提供了简单且方便的定时功能,但在使用时需要注意其运行模式和准确性限制。
NSTimer默认被谁持有?
NSTimer 默认是由其所在的 run loop 持有的。具体而言,当你使用 scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 方法创建并调度一个 NSTimer 时,它会自动被添加到当前线程的默认 run loop 模式中,并被 run loop 强引用。
详细解释
-
自动调度计时器:
当你使用
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:方法创建一个计时器时,该计时器会立即被添加到当前线程的 run loop 中。objective-c[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES];这种方法返回的计时器会被 run loop 强引用,因此你不需要手动持有它,计时器会自动开始工作。
-
手动添加计时器:
如果你使用
timerWithTimeInterval:target:selector:userInfo:repeats:方法创建计时器,你需要手动将其添加到一个 run loop 中:objective-cNSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];在这种情况下,只有当计时器被添加到 run loop 中时,run loop 才会持有该计时器。
持有关系示意图
plaintext
-----------------------
| |
| NSRunLoop |
| (strong) |
| |
| --------------- |
| | NSTimer | |
| --------------- |
| |
-----------------------
计时器的生命周期
- 开始:
- 当
NSTimer被添加到 run loop 时,run loop 会持有它,并在指定的时间间隔后触发。
- 当
- 执行:
- 计时器触发后,会调用指定的目标对象的方法(selector)。
- 重复或停止:
- 对于重复计时器,run loop 会继续持有它,直到计时器被手动无效化(
invalidate)。 - 对于非重复计时器,触发一次后计时器会自动无效化,并从 run loop 中移除。
- 对于重复计时器,run loop 会继续持有它,直到计时器被手动无效化(
- 无效化:
- 调用
invalidate方法会使计时器无效,run loop 会释放对计时器的持有。 - 一旦无效化,计时器将不再触发,并且无法重新启用。
- 调用
示例代码
objective-c
- (void)startTimer {
// 创建并调度一个计时器
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerFired:)
userInfo:nil
repeats:YES];
}
- (void)timerFired:(NSTimer *)timer {
// 计时器触发时执行的代码
NSLog(@"Timer fired!");
}
- (void)stopTimer {
// 停止计时器
[self.timer invalidate];
self.timer = nil;
}
总结
NSTimer 默认由其所在的 run loop 持有。这意味着只要计时器被正确添加到 run loop 中,你不需要额外持有它。记得在适当的时候无效化计时器以避免潜在的内存泄漏。