解决NSTimer在滑动时停止工作的问题
创建定时器时,如果使用类似 +[NSTimer scheduledXXX]
的方法创建,则会自动添加到默认 RunLoop
模式并开始执行定时器。
而使用类似 +[NSTimer timerWithXXX]
的方法创建,则需要手动添加到 RunLoop
。
添加到 RunLoop
时使用 NSRunLoopCommonModes
,则滑动不会对定时器造成影响:
__block int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", count++);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
NSRunLoopCommonModes
并不是一个模式,它只是一个标记,标记为通用模式。能够运行在NSDefaultRunLoopMode
和UITrackingRunLoopMode
模式。
控制线程生命周期(线程保活)
// 自定义一个NSThread子类,用于监控线程销毁
@interface JFThread : NSThread
@end
@implementation JFThread
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
@interface ViewController ()
@property (nonatomic, strong) JFThread *thread;
@property (nonatomic, assign) BOOL isStopped;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.thread = [[JFThread alloc] initWithBlock:^{
NSLog(@"%s %@ ---begin", __func__, [NSThread currentThread]);
// 往RunLoop里面添加Source/Timer/Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
// 运行RunLoop,开启一个无限循环,无法停止,用于开启一个在程序运行过程中永不销毁的线程。内部会不断的调用 runMode:beforeDate:
// [[NSRunLoop currentRunLoop] run];
// 防止控制器释放后,weakSelf为空。导致while循环条件成立
while (weakSelf && !weakSelf.isStopped) {
// 执行完这行代码,就会睡眠,而不会一直不停循环执行。等待任务执行后,才会继续执行。这相当于是内核层的线程睡眠,不会持续占用CPU资源
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
NSLog(@"runMode:beforeDate:");
}
NSLog(@"%s %@ ---end", __func__, [NSThread currentThread]);
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (self.thread) {
// 在指定线程执行任务
[self performSelector:@selector(task) onThread:self.thread withObject:nil waitUntilDone:NO];
// waitUntilDone 参数如果传 YES,则后面的代码会等待 performSelector 的 SEL 执行完毕后才会执行
}
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)task
{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)stop
{
self.isStopped = YES;
NSLog(@"%s %@", __func__, [NSThread currentThread]);
// 相当于停止掉一次runMode:beforeDate:执行后的睡眠等待任务,所以这行代码是不能省略的
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
}
- (void)dealloc
{
if (self.thread) {
// 停止RunLoop
[self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
}
NSLog(@"%s", __func__);
}
@end
封装保活线程工具类
#import <Foundation/Foundation.h>
typedef void(^JFLifecycleThreadTask)(void);
NS_ASSUME_NONNULL_BEGIN
@interface JFLifecycleThread : NSObject
/// 执行一个任务
/// @param target 目标
/// @param action 方法
/// @param object 参数
- (void)executeTaskWithTarget:(id)target action:(SEL)action object:(_Nullable id)object;
/// 执行一个任务
/// @param task 任务
- (void)executeTask:(JFLifecycleThreadTask)task;
@end
NS_ASSUME_NONNULL_END
#import "JFLifecycleThread.h"
@interface JFLifecycleThread ()
@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, assign, getter=isStopped) BOOL stopped;
@end
@implementation JFLifecycleThread
- (instancetype)init
{
if (self = [super init]) {
__weak typeof(self) weakSelf = self;
self.thread = [[NSThread alloc] initWithBlock:^{
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.isStopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
[self.thread start];
}
return self;
}
/// 执行一个任务
/// @param target 目标
/// @param action 方法
/// @param object 参数
- (void)executeTaskWithTarget:(id)target action:(SEL)action object:(_Nullable id)object
{
if (!self.thread) return;
[target performSelector:action onThread:self.thread withObject:object waitUntilDone:NO];
}
/// 执行一个任务
/// @param task 任务
- (void)executeTask:(JFLifecycleThreadTask)task
{
if (!self.thread && task) return;
[self performSelector:@selector(__task:) onThread:self.thread withObject:task waitUntilDone:NO];
}
// 停止RunLoop和销毁线程
- (void)__stop
{
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
}
// 执行block包装的任务
- (void)__task:(JFLifecycleThreadTask)task
{
task();
}
- (void)dealloc
{
if (!self.thread) return;
[self performSelector:@selector(__stop) onThread:self.thread withObject:nil waitUntilDone:YES];
}
@end
使用工具类:
@interface ViewController ()
@property (nonatomic, strong) JFLifecycleThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[JFLifecycleThread alloc] init];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.thread executeTask:^{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}];
}
@end
如果采用 C
语言 API 来添加事件源和运行 RunLoop
,可以通过设置 CFRunLoopRunInMode
第三个参数为 false
,这样就不需要我们再通过一个 while
循环来防止 RunLoop
处理一个事件源后就返回。
self.thread = [[NSThread alloc] initWithBlock:^{
CFRunLoopSourceContext context = {};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
// 第三个参数 returnAfterSourceHandled: 处理一个事件源后就返回
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
}];
[self.thread start];