1. 程式人生 > >RunLoop六:在實際開發中的應用 之 控制執行緒生命週期(執行緒保活)

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], <WYTread: 0x60000026ed80>{number = 3, name = (null)}
 -[WYTread dealloc]

三、預設執行緒舉例2
在開發中經常會遇到,經常在後臺做事情。如果有這種需求的話,使用預設執行緒非常不合適。
這個時候需要的是:讓執行緒死亡,執行緒才死亡;不讓執行緒死亡,執行緒就繼續存活。
例如:在 touchesBegan 中新增執行緒。 這種就是當你點選,建立愛你一個執行緒。結束執行緒。多次點選執行緒,多次建立執行緒,多次銷燬執行緒。

@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)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], <WYTread: 0x60400027bc80>{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<UITouch *> *)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: 方法 問題
建立一個導航控制器,如圖:
在這裡插入圖片描述

  • 點選 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。
    在這裡插入圖片描述

  • 再次執行程式。點選橘色介面的back返回按鈕,會發現。呼叫了 ViewController 介面的 dealloc 方法。

  • 因為 控制器 並不被 shelf.thread 所擁有。

  • 這就可以說明,initWithTarget 的那個方法會將 控制器強引用。

  • 問題: 執行緒沒有被釋放,只有控制器釋放了。