iOS 多线程基础之 NSThread
前言
通常在 iOS 中,有三種比較常見的方式實現多線程,分別是 NSThread,GCD 和 NSOperation。本文主要介紹它們當中使用頻率較低的 NSThread。
文章目錄
- 前言
- NSThread
- 1. 線程創建
- 方法一:常規方法創建新線程
- 方法二:detachNewThread 系列類方法
- 方法三:NSObject + NSThreadPerformAdditions 分類中提供的方法
- 2. 線程優先級
- 優先級實驗
- 新的優先級屬性
- 3. 線程間通信
- 線程間通信實例
- 4. 常用屬性和方法
- 改變線程狀態的方法
- 判斷線程狀態的方法
- 主線程判斷的方法
- 執行線程任務的方法
- NSThread 常用類方法
- 線程休眠相關方法
- 參考資料
NSThread
NSThread 是 Foundation 框架中封裝的線程類,其對象就表示程序的一個線程。
1. 線程創建
我們有下面三種方法可以創建新線程去執行任務。
方法一:常規方法創建新線程
這種方法比較常規,通過對象創建和初始化方法進行新線程的創建。雖然寫代碼的步驟看起來比較復雜,但是可以獲取到線程的對象,可以對線程進行名稱、優先級之類的個性化修改。
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil];NSThread *thread = [[NSThread alloc] initWithBlock:^{NSLog(@"Run %@", [NSThread currentThread]); }];// 需要手動調用 start 方法 [thread start];threadRun 方法表示線程將要執行的任務
- (void)threadRun {NSLog(@"Run %@", [NSThread currentThread]); }方法二:detachNewThread 系列類方法
我們可以通過 detachNewThread 系列的類方法創建新線程來執行任務,代碼會比較簡潔。但是這些方法都是沒有返回值的,這就意味著我們無法獲取到新創建的線程,進行無法修改線程設置。
[NSThread detachNewThreadSelector:@selector(threadRun) toTarget:self withObject:nil];[NSThread detachNewThreadWithBlock:^{NSLog(@"Run %@", [NSThread currentThread]); }];方法三:NSObject + NSThreadPerformAdditions 分類中提供的方法
NSObject + NSThreadPerformAdditions 分類中提供的 performSelectorInBackground: withObject: 方法也可以創建新線程執行任務,它的使用情況和方法二相似。
[self performSelectorInBackground:@selector(threadRun) withObject:nil];2. 線程優先級
線程是具有優先級的,線程的優先級代表 CPU 調度的優先級,優先級高的線程更容易得到 CPU 資源執行任務。對于 NSThread 對象,我們可以通過 threadPriority 屬性來修改它的優先級,優先級的取值范圍為 [0.0,1.0][0.0, 1.0][0.0,1.0],默認優先級為 0.5。
優先級實驗
我們創建三個線程,優先級分別為 1.0 / 0.1 / 默認,讓它們執行同一個任務,根據控制臺輸出結果,查看各優先級線程的任務執行情況。
// 設置線程 A 的優先級為 1.0 NSThread *threadA = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil]; threadA.name = @"threadA"; threadA.threadPriority = 1.0; [threadA start];// 設置線程 B 的優先級為 0.1 NSThread *threadB = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil]; threadB.name = @"threadB"; threadB.threadPriority = 0.1; [threadB start];// 讓線程 C 保持默認優先級(0.5) NSThread *threadC = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil]; threadC.name = @"threadC"; [threadC start];// 線程要執行的任務 - (void)threadRun {for (int i = 0; i < 100; i++) {NSLog(@"Run %@ - %d", [NSThread currentThread].name, i);} }根據上面例子的執行結果可以看出來,優先級高的線程能更快地完成它需要執行的任務。
新的優先級屬性
從官方的注釋中,我們發現 threadPriority 屬性在將來會被棄用,用 qualityOfService 作為新的線程優先級。
// 新版的線程優先級 @property NSQualityOfService qualityOfService;typedef NS_ENUM(NSInteger, NSQualityOfService) {// 和圖形處理相關的任務,比如滾動和動畫NSQualityOfServiceUserInteractive = 0x21,// 用戶請求的任務,但是不需要精確到毫秒級。例如,用戶請求打開電子郵件App來查看郵件NSQualityOfServiceUserInitiated = 0x19,// 周期性的用戶請求任務。比如,電子郵件App可能被設置成每5分鐘自動檢測新郵件NSQualityOfServiceUtility = 0x11,// 后臺任務,對這些任務用戶可能并不會察覺NSQualityOfServiceBackground = 0x09,// 默認的優先級,優先級在 Utility 之前NSQualityOfServiceDefault = -1 } API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));3. 線程間通信
通常線程間通信主要有下面兩種情形:
- 一個線程的數據傳遞給另一個線程
- 一個線程的任務執行完后,轉到另一個線程繼續執行任務
與 NSThread 有關的線程間通信實現的方法在 NSObject + NSThreadPerformAdditions 分類中,常用的是下面兩個方法。
// 到主線程執行指定任務 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;// 到指定線程執行指定任務 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait線程間通信實例
這里用一個經典實例,來對線程間通信進行講解。我們要在網上下載圖片,然后展示在手機屏幕上顯示。為了不阻塞主線程,我們需要異步進行圖片的下載,又因為 iOS 不能在子線程更新 UI,我們需要在圖片下載完成之后回到主線程將下載好的圖片更新到 UIImageView 上。
@interface ViewController ()// 用于展示圖片 @property (nonatomic, strong) UIImageView *imageView;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];[self.view addSubview:self.imageView];// 設置約束[self.imageView mas_makeConstraints:^(MASConstraintMaker *make) {make.center.equalTo(self.view);make.size.mas_equalTo(CGSizeMake(216, 384));}];// 創建線程NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage) object:nil];// 啟動線程[thread start]; }#pragma mark - Actions// 后臺線程處理方法 - (void)downloadImage {// 耗時操作,下載網絡圖片NSURL *url = [NSURL URLWithString:@"https://pics3.baidu.com/feed/b151f8198618367afc010b4f3e2a1dd2b21ce573.jpeg?token=a80568b00de1ae2814069f2f70241a96"];NSData *imgData = [NSData dataWithContentsOfURL:url];// 執行耗時操作完成,返回主線程處理[self performSelectorOnMainThread:@selector(updateUIWithData:) withObject:imgData waitUntilDone:YES]; }// 需要在主線程執行的操作 - (void)updateUIWithData:(NSData *)data {// 刷新 UI 的方法UIImage *image = [UIImage imageWithData:data];self.imageView.image = image; }#pragma mark - Getter- (UIImageView *)imageView {if (!_imageView) {_imageView = [[UIImageView alloc] init];_imageView.backgroundColor = [UIColor grayColor];}return _imageView; }@end4. 常用屬性和方法
因為前面上面提到的 NSThread 使用方式都有更加便捷的 GCD 函數代替,所以相比之下,下面這些方法反而會更加常用。
改變線程狀態的方法
// 啟動線程執行任務,創建 -> 就緒 [thread start]; // 標記線程為 cancelled 狀態 [thread cancel]; // 退出線程執行, -> 終止 [NSThread exit];判斷線程狀態的方法
// 判斷任務是否被取消 BOOL isCancelled = [thread isCancelled]; // 判斷任務是否正在執行 BOOL isExecuting = [thread isExecuting]; // 判斷任務是否執行完成 BOOL isFinished = [thread isFinished];主線程判斷的方法
// 判斷 thread 是否為主線程 BOOL isMainThread = [thread isMainThread];執行線程任務的方法
// 在當前線程中執行 thread 對象的任務 [thread main];NSThread 常用類方法
// 判斷是否處在多線程環境 [NSThread isMultiThreaded];// 獲取當前線程 [NSThread currentThread]// 獲取主線程 [NSThread mainThread];線程休眠相關方法
// 讓線程休眠到 data 所在時間 [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; // 讓線程休眠 ti 秒 [NSThread sleepForTimeInterval:1.0];參考資料
- NSThread | Apple Developer Documentation
總結
以上是生活随笔為你收集整理的iOS 多线程基础之 NSThread的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iOS - 数据持久化之 FMDB 的使
- 下一篇: 有 OC 经验的程序员快速学习 Swif