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 的那個方法會將 控制器強引用。
-
問題: 執行緒沒有被釋放,只有控制器釋放了。