iOS 常驻线程

2,341 阅读6分钟

目录

1.结合runloop实现常驻线程

2.常驻线程遇到的坑及解决方案

3.常驻线程的封装


1.结合runloop实现常驻线程

我们通过代码简单实现常驻线程

@interface ViewController ()
@property (strong, nonatomic) NSThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadStart) object:nil];
    [self.thread start];
    
}

- (void)threadStart{
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    //往RunLoop里面添加Source\Timer\Observer 避免runloop里面没有事件执行退出
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];

    NSLog(@"%s ----end----", __func__);
}

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

// 子线程需要执行的任务
- (void)test
{
    NSLog(@"%s %@ 执行了test", __func__, [NSThread currentThread]);
}

让线程常驻执行任务的几个关键点:
1.创建子线程需要启动runloop,不然执行完线程里的任务,线程就直接被回收了,达不到保活的目的。
2.启动runloop,需要往里面添加Source\Timer\Observer,保证里面有事件可执行,否则runloop退出,线程里的任务执行完,线程还是会被回收,添加以下代码即可:

   [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];

3.任务需要用performSelector方法向指定的线程里面添加才能在相应线程里执行。

  • 点击屏幕 在子线程执行test方法,打印结果可以发现方法确实在子线程执行了: 截屏2021-06-03 下午7.49.29.png

2.常驻线程遇到的坑及解决方案

上面讲了使用runloop让线程常驻的基本方法,但是一般我们业务需求中可能需要控制子线程的生命周期,比如我在一个听音乐的Controller里面使用常驻子线程执行一个每隔一秒就统计一次听歌时长的任务,那么当这个Controller退出时,这个常驻子线程也需要销毁。根据这个需求我们用代码实现一下:\

创建一个ThreadViewController控制器,里面添加一个常驻线程,点击“开始执行”按钮 则开启常驻线程执行任务:

//LiveThread.h
@interface LiveThread : NSThread

@end

//LiveThread.m
@implementation LiveThread
- (void)dealloc
{
    NSLog(@"%s", __func__);
}

@end
#import "ThreadViewController.h"
#import "LiveThread.h"
@interface ThreadViewController ()
@property (nonatomic,strong) NSThread *thread;
@end

@implementation ThreadViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UIButton *startBtn = [UIButton buttonWithType:UIButtonTypeSystem];
    [self.view addSubview: startBtn];
    [startBtn setTitle:@"开始执行" forState:UIControlStateNormal];
    [startBtn addTarget:self action:@selector(startBtnClick) forControlEvents:UIControlEventTouchUpInside];
    startBtn.frame = CGRectMake(100, 150, 100, 30);

    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadStart) object:nil];
    [self.thread start];
}

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

    //往RunLoop里面添加Source\Timer\Observer 避免runloop里面没有mode而退出
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];

    [[NSRunLoop currentRunLoop] run];
    NSLog(@"%s ----end----", __func__);
}

-(void)startBtnClick{
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 子线程需要执行的任务
- (void)test
{
    NSLog(@"%s %@ 执行了test", __func__, [NSThread currentThread]);
}
@end

当我们执行常驻任务时,点击控制器返回按钮返回:发现ThreadViewController和LiveThread都没有被释放,发生了内存泄漏,这里遇到第一个坑:

1. 初始化强引用导致内存泄漏:

这个创建方法对self是强引用,而上面Controller对thread也是强引用,相互引用导致内存泄漏
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadStart) object:nil];

改变创建方式,使用block:

    self.thread = [[LiveThread alloc] initWithBlock:^{
       NSLog(@"%@----begin----", [NSThread currentThread]);
       // 往RunLoop里面添加Source\Timer\Observer
       [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
       [[NSRunLoop currentRunLoop] run];
       NSLog(@"%@----end----", [NSThread currentThread]);
    }];

使用block创建后,ThreadViewController点击返回会调用dealloc释放了,但是LiveThread依旧没有被释放。

这是因为我们在子线程开启的runloop并没有停止,导致线程不能被回收

于是添加一个停止按钮,调用在子线程CFRunLoopStop方法:

-(void)stopBtnClick{
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];

}

-(void)stopThread{
    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
}

点击停止按钮发现,还是没有效果。

进一步探究发现 :使用[[NSRunLoop currentRunLoop] run] 启动runloop,是无法被停止的。

所以我们换一个方式并添加stoped参数去控制runloop启动:

@interface ThreadViewController ()
@property (nonatomic,assign ) BOOL stopped;
@end
- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    self.thread = [[LiveThread alloc] initWithBlock:^{
       NSLog(@"%@----begin----", [NSThread currentThread]);
       // 往RunLoop里面添加Source\Timer\Observer
       [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (weakSelf && !weakSelf.stopped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
       NSLog(@"%@----end----", [NSThread currentThread]);
    }];
    [self.thread start];
}
-(void)stopBtnClick{
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}

-(void)stopThread{
    // 设置标记为YES
    self.stopped = YES;
    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
}

经过上面的相关操作,点击停止stopBtnClick后发现"----end----"打印终于执行了,即runloop被停止了,ThreadViewController返回,Thread也正常被释放了。

但是我们想要Thread随着ThreadViewController释放而释放,这要怎么处理呢?
把stop方法放在delloc里面即可 不过为了安全,performSelector方法中的waitUntilDone参数要设置为YES,避免Controller过早释放造成崩溃。 完整实现代码如下:

#import "ThreadViewController.h"
#import "LiveThread.h"
@interface ThreadViewController ()
@property (nonatomic,strong) NSThread *thread;
@property (nonatomic,assign ) BOOL stopped;
@end

@implementation ThreadViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton *startBtn = [UIButton buttonWithType:UIButtonTypeSystem];
    [self.view addSubview: startBtn];
    [startBtn setTitle:@"开始执行" forState:UIControlStateNormal];
    [startBtn addTarget:self action:@selector(startBtnClick) forControlEvents:UIControlEventTouchUpInside];
    startBtn.frame = CGRectMake(100, 150, 100, 30);
    
    __weak typeof(self) weakSelf = self;
    self.thread = [[LiveThread alloc] initWithBlock:^{
       NSLog(@"%@----begin----", [NSThread currentThread]);
       // 往RunLoop里面添加Source\Timer\Observer
       [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (weakSelf && !weakSelf.stopped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
       NSLog(@"%@----end----", [NSThread currentThread]);
    }];
    [self.thread start];
}

-(void)startBtnClick{
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 子线程需要执行的任务
- (void)test
{
    NSLog(@"%s %@ 执行了test", __func__, [NSThread currentThread]);
}

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

}

-(void)stopThread{
    self.stopped = YES;
    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
    // 清空线程
    self.thread = nil;
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    [self stop];
}
@end

总结一下需要注意的点:

1.初始化创建线程最好使用block的方式 2.使用[NSRunLoop currentRunLoop] run]方法将无法停止当前线程Runloop 3.performSelector的waitUntilDone参数设置,表示执行完此线程的任务才会继续往下执行。

3.常驻线程的封装

前面两节已经清楚的讲了线程保活相关操作以及要注意的问题,实际应用中我们可以把这块功能给封装出来方便项目各个模块使用:

//AliveThread.h
#import <Foundation/Foundation.h>
typedef void (^AliveThreadTask)(void);
@interface AliveThread : NSObject
/**
 在当前子线程执行一个任务
 */
- (void)executeTask:(AliveThreadTask)task;

/**
 结束线程
 */
- (void)stop;

@end

//AliveThread.m
#import "AliveThread.h"
//LiveThread主要用于打印释放情况
@interface LiveThread : NSThread
@end
@implementation LiveThread
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

@interface AliveThread ()

@property (nonatomic,strong) LiveThread * liveThread;
@property (nonatomic, assign) BOOL stopped;

@end

@implementation AliveThread

- (instancetype)init
{
    if (self = [super init]) {
        self.stopped = NO;
        
        __weak typeof(self) weakSelf = self;
        
        self.liveThread = [[LiveThread alloc] initWithBlock:^{
            NSLog(@"------常驻线程开启-------");
            [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
            
            while (weakSelf && !weakSelf.stopped) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
            NSLog(@"------常驻线程结束------");
        }];
        
        [self.liveThread start];
    }
    return self;
}


- (void)executeTask:(AliveThreadTask)task
{
    if (!self.liveThread || !task) return;
    
    [self performSelector:@selector(realizeTask:) onThread:self.liveThread withObject:task waitUntilDone:NO];
}


-(void)realizeTask:(AliveThreadTask)task{
    
    task();
}

- (void)stop
{
    if (!self.liveThread) return;
    [self performSelector:@selector(liveThreadStop) onThread:self.liveThread withObject:nil waitUntilDone:YES];
}


-(void)liveThreadStop{
    self.stopped = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.liveThread = nil;
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
    [self stop];
}
@end

使用:

#import "ViewController.h"
#import "AliveThread.h"
@interface ViewController ()

@property (strong, nonatomic) AliveThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.thread = [[AliveThread alloc] init];
    [self.thread executeTask:^{
        NSLog(@"执行任务 - %@", [NSThread currentThread]);
        NSLog(@"111111111");
        NSLog(@"222222");
        NSLog(@"3333333");
    }];
    
}
@end
  • 关于常驻线程就写到这啦,有纰漏之处,欢迎指正~