iOS 线程保活如何实现?

标签: 编程学习 iOS学习

iOS开发中经常会用到多线程的技术,每次使用都需要创建线程,这样会消耗很多的资源,能不能让线程常驻呢?

线程使用

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    self.thread = [[DCThread alloc] initWithBlock:^{
        NSLog(@"---%@-----执行完毕", [NSThread currentThread]);
    }];
    [self.thread start];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"点击了");
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

-  (void)test {
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

看代码,我们创建了一个线程,block中的代码执行完成之后点击界面发现该线程已经不能用了,那是因为该线程在执行完之后就退出了,如何处理这个问题呢?答案就是使用 Runloop 技术使线程保活,不使用时睡眠,使用时激活就行了

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    self.thread = [[DCThread alloc] initWithBlock:^{
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];

        NSLog(@"---%@-----执行完毕", [NSThread currentThread]);
    }];
    [self.thread start];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"点击了");
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

-  (void)test {
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

添加上 Runloop 的代码之后就发现会卡在 run 那里,只要点击界面就会执行 test 方法。 但是这里有一个问题,就是 run 这个方法的官方解释

If you want the run loop to terminate, you shouldn't use this method
你还想从 runloop 里面退出来,就不能用 run 方法。

这样就无法释放了,所以我们做一下修改,使用有模式的方式去运行

[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
/// 不这样写也只是执行一次
while (1) {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

使用 while 循环是为了保证能够一直停留在这里,但是这样又不可控,所以进行改造一下,比较完美

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    self.isStoped = false;
    __weak typeof(self) weakSelf = self;
    self.thread = [[DCThread alloc] initWithBlock:^{

        // 往RunLoop里面添加Source\Timer\Observer
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (weakSelf && !weakSelf.isStoped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }

        NSLog(@"---%@-----执行完毕", [NSThread currentThread]);
    }];
    [self.thread start];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"点击了");
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

- (void)test {
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

- (IBAction)stop:(id)sender {
    if (!self.thread) return;
    // 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}

- (void)stopThread {
    // 设置标记为YES
    self.isStoped = YES;

    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s %@", __func__, [NSThread currentThread]);

    // 清空线程
    self.thread = nil;
}

- (void)dealloc {
    NSLog(@"controller 死了");
    [self stopThread];
}

工具封装

既然是为了保活线程重复利用,那直接写在 ViewController 中肯定不行,我们这里封装一下

@interface DCThread()
///不允许外部调用
@property (strong, nonatomic) NSThread *innerThread;
///控制是否停止
@property (assign, nonatomic, getter=isStopped) BOOL stopped;
@end

@implementation DCThread

- (instancetype)init {
    if (self = [super init]) {
        self.stopped = NO;

        __weak typeof(self) weakSelf = self;
        self.innerThread = [[NSThread alloc] initWithBlock:^{
            [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];

            while (weakSelf && !weakSelf.isStopped) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
        }];
        /// 自动开启
        [self.innerThread start];
    }
    return self;
}

/// 外部暴露的接口,执行一个任务
- (void)executeTask:(DCThreadBlock)task {
    if (!self.innerThread || !task) return;
    [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}

- (void)dealloc {
    /// 自动释放
    NSLog(@"%s", __func__);
    [self stopTask];
}

///自动调用停止
- (void)stopTask {
    if (!self.innerThread) return;
    [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}

- (void)__stop {
    self.stopped = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.innerThread = nil;
}

- (void)__executeTask:(DCThreadBlock)task {
    task();
}
@end