RunLoop的原理探索-常用场景

/ 0

解决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 并不是一个模式,它只是一个标记,标记为通用模式。能够运行在 NSDefaultRunLoopModeUITrackingRunLoopMode 模式。

控制线程生命周期(线程保活)

// 自定义一个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];