RunLoop六:在实际开发中的应用 之 控制线程生命周期(线程保活)

一、前言
OC 的程序员大多数用过的 AFNetwork 这个网络请求框架。这个框架中就使用了 RunLoop 技术,去控制子线程的生命周期。
相当于 它创建了一个子线程,这个子线程会一直都在内存中,不会死亡。当某个时间段需要子线程工作时,会告诉子线程需要做什么?过一段时间,又有工作了就又会告诉子线程需要做什么? AFNetwork 这个框架会一直让子线程 停留在 内存中。
这种情况适用于: 经常在子线程做事情。
如果完成一个任务就把子线程销毁。下次再做事情再次开启一个新的子线程。如此循环创建、销毁,会耗费性能。这个时候就可以创建一个不死的 子线程,节省性能。

二、默认线程举例1
为了监控 线程的存活,自己创建一个类A,这个类继承 NSTread.
在 ViewController 中 创建

#import "WYTread.h"
@implementation WYTread
- (void)dealloc {
    NSLog(@"%s",__func__);
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    WYTread *tread = [[WYTread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [tread start];
}

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

打印结果在下面,可以看到,打印完 run 方法的内容后,线程就 dealloc 了。这也符合默认情况(线程执行完毕就没了)

 -[ViewController run], {number = 3, name = (null)}
 -[WYTread dealloc]

三、默认线程举例2
在开发中经常会遇到,经常在后台做事情。如果有这种需求的话,使用默认线程非常不合适。
这个时候需要的是:让线程死亡,线程才死亡;不让线程死亡,线程就继续存活。
例如:在 touchesBegan 中添加线程。 这种就是当你点击,创建爱你一个线程。结束线程。多次点击线程,多次创建线程,多次销毁线程。

@implementation ViewController
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    WYTread *tread = [[WYTread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [tread start];
}

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

打印结果为:
在这里插入图片描述

四、不靠谱的 循环线程 - while(1)

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

while(1): 这个的确会让 线程不死,但当你想要做其他事情的时候,不能使用了。因为卡住了。
所以这种做法没有意义。

五、靠谱的循环线程 - RunLoop
runloop 会在不使用线程的时候,进入休眠状态。性能高。
如何创建 runloop?其实不用特意创建,只需要获取当前的runloop 然后 run 就好。因为在你第一次获取runloop的时候,会自动帮你创建好。

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

上面的代码可以 让 runloop 跑起来,但是跟 没有开启runloop 一样,一跑起来就结束了。如下图打印:
在这里插入图片描述
这是因为当RunLoop里面的 Mode里没有任何Source0/Source1/Timer/Observer时,RunLoop会立马退出

所以要往 runloop 中 添加 Source/Timer/Observer。

- (void) run {
    NSLog(@"%s, %@",__func__,[NSThread currentThread]);
    
    // 往  runloop 中 添加  Source/Timer/Observer
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init ]forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    
    NSLog(@"---- end ----");
}

这个时候再次运行程序,会发现-[ViewController run], {number = 3, name = (null)} 只打印了 run 方法的第一句,没有跟刚才一样 打印 end 。 这就说明 runloop 起作用了。

六、在子线程做事情
一开始 thread 的 run 方法是用来 创建 一个 runloop 的。不是用来做线程里面做的事情的。
点击界面,就做一些事情。

#import "ViewController.h"
#import "WYTread.h"

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

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

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

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // waitUntilDone : No: 代表执行 test 方法里面的东西。同时执行 主线程的内容
    // Yes: 代表 不在往下执行,直到 test方法 里面的内容执行完毕再接着往下执行。
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
    NSLog(@"aaa");
}

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

- (void) run {
    NSLog(@"%s, %@",__func__,[NSThread currentThread]);
    
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init ]forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    
    NSLog(@"---- end ----");
}
@end

七、initWithTarget: selector:object: 方法 问题
创建一个导航控制器,如图:
RunLoop六:在实际开发中的应用 之 控制线程生命周期(线程保活)_第1张图片

  • 点击 back 关闭 橘色界面,没有线程死亡的打印。

  • 在 viewController 界面写下 dealloc 方法。然后打印下。发现。dealloc 方法没有被调用。

  • 当我们把 viewDidLoad方法的 MJThread 两行代码注释。再次执行程序。点击橘色界面的back返回按钮,会发现。调用了 ViewController 界面的 dealloc 方法。

  • 那可能会问是不是因为 self.thread = [[WYTread alloc] initWithTarget:self selector:@selector(run) object:nil]; 这行代码的 initWithTarget 的self 对控制器产生了强引用。线程内部对控制器产生了强引用;@property (strong, nonatomic) MJThread *thread; 这句话又对线程产生了强引用。

  • 这种你强引用我,我强引用你。会导致循环引用。就不能释放,不能死亡。

  • 那如果把 self.thread = [[WYTread alloc] initWithTarget:self selector:@selector(run) object:nil]; 改写下,变成 block。
    RunLoop六:在实际开发中的应用 之 控制线程生命周期(线程保活)_第2张图片

  • 再次运行程序。点击橘色界面的back返回按钮,会发现。调用了 ViewController 界面的 dealloc 方法。

  • 因为 控制器 并不被 shelf.thread 所拥有。

  • 这就可以说明,initWithTarget 的那个方法会将 控制器强引用。

  • 问题: 线程没有被释放,只有控制器释放了。

你可能感兴趣的:(iOS底层)