前言
在日常開發中,多線程的使用能幫助我們解決很多問題,比如大量資料的運算,複雜程式的執行,以及利用鎖來實作一些需求,本系列文章主要介紹 iOS 中多線程實作技術的用法。
iOS:多線程(一) —— pthread
關于 pthread 的介紹和使用請檢視之前的文章,本篇文章針對 NSThread 來贅述。
關于 NSThread
NSThread 是蘋果官方提供給我們的一種面向對象的輕量級多線程解決方案,一個 NSThread 對象代表一個線程,需要程式員手動管理線程的生命周期,處理線程同步等問題。
NSThread 使用
常用方法
在日常開發中,我們經常會用
[NSThread currentThread]
來擷取目前線程,便于開發調試,這是最常用的一個方法,除此之外,下面的這幾個方法,使用頻率也是非常高,基于
NSObject
的
NSThreadPerformAdditions
分類中的方法,繼承自
NSObject
的子類都可以很友善的調用。
// 目前線程睡到指定時間
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
// 線程沉睡時間間隔 常用在設定啟動頁間隔
[NSThread sleepForTimeInterval:1.0];
// 傳回調用堆棧資訊 可用于調試
// [NSThread callStackSymbols] return NSArray
// [NSThread callStackReturnAddresses] return NSArray
NSLog(@"callStackSymbols : %@", [NSThread callStackSymbols]);
NSLog(@"callStackReturnAddresses : %@", [NSThread callStackReturnAddresses]);
@interface NSObject (NSThreadPerformAdditions)
// 指定方法在主線程中執行
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
// 指定方法在某個線程中執行
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
// equivalent to the first method with kCFRunLoopCommonModes
// 指定方法在開啟的子線程中執行
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
建立線程
-
執行個體方法建立線程
執行個體方法建立線程,可以根據需要設定參數,調用
才能開啟線程,調用- start()
取消線程- cancel()
// SEL
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(nsThreadMethod:) object:@"thread"];
NSThread *thread2 = [[NSThread alloc] init];
// block 方式
NSThread *thread3 = [[NSThread alloc] initWithBlock:^{
NSLog(@"block 建立 thread %s : %@", __func__, [NSThread currentThread]);
}];
// 設定名稱
thread1.name = @"thread1";
// 設定線程優先級 排程優先級的取值範圍是0.0 ~ 1.0,預設0.5,值越大,優先級越高。
thread3.threadPriority = 0.0;
// 啟動線程
[thread1 start];
// 線程是否正在執行
if ([thread3 isExecuting]) {
NSLog(@"thread1 is executing! ");
}
// 取消線程
[thread1 cancel];
// 線程是否撤銷
if ([thread1 isCancelled]) {
NSLog(@"thread1 canceled!");
}
// 線程是否執行結束
if ([thread3 isFinished]) {
NSLog(@"thread3 is finished!");
}
-
類方法建立線程
類方法建立
不需要再調用NSThread
方法,設定參數是通過類方法設定start
// block 方式
[NSThread detachNewThreadWithBlock:^{
NSLog(@"類方法 block 建立 thread : %s : %@", __func__, [NSThread currentThread]);
}];
// SEL 方式
[NSThread detachNewThreadSelector:@selector(nsThreadMethod:) toTarget:self withObject:nil];
/*
[NSThread currentThread]; 擷取目前線程
[NSThread isMultiThreaded]; 目前代碼運作線程是否為子線程
*/
// 目前線程睡到指定時間
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
// 線程沉睡時間間隔 常用在設定啟動頁間隔
[NSThread sleepForTimeInterval:1.0];
// 擷取線程優先級 / 設定優先級
double priority = [NSThread threadPriority];
NSLog(@"目前線程優先級 : %f", priority);
[NSThread setThreadPriority:0.9];
線程間通信
通常,例如我們有個網絡請求是在子線程中執行,請求成功後我麼要回到主線程中重新整理UI,這是時候我們就需要了解子線程和主線程之間的通信,
NSThread
為我們提供了解決方案,調用
NSObject
和
NSObject (NSThreadPerformAdditions)
分類中的方法,所有繼承自
NSObject
執行個體化對象都可調用以下方法
// 指定方法在主線程中執行
[self performSelectorOnMainThread:@selector(performMethod:) // 要執行的方法
withObject:nil // 執行方法時,要傳入的參數 類型為 id
waitUntilDone:YES]; // 目前線程是否要被阻塞,直到主線程将我們指定的代碼塊執行完,目前線程為主線程,設定為YES時,會立即執行,為NO時加入到RunLoop中在下一次運作循環時執行
[self performSelectorOnMainThread:@selector(performMethod:)
withObject:nil
waitUntilDone:YES
modes:@[@"kCFRunLoopDefaultMode"]];
// 指定方法在某個線程中執行
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// 在目前線程上執行操作,調用 NSObject 的 performSelector:相關方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
// 指定方法在開啟的子線程中執行 (相當于建立了一個子線程,并且執行方法)
[self performSelectorInBackground:@selector(performMethod:) withObject:nil];
舉個例子,我們來模拟網絡請求成功回到線程重新整理 UI 的實作
// 開辟子線程模拟網絡請求
[NSThread detachNewThreadWithBlock:^{
NSLog(@"類方法 block 建立 thread : %s : %@", __func__, [NSThread currentThread]);
// 模拟網絡請求耗時操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"網絡請求中 %@",[NSThread currentThread]);
}
NSLog(@"網絡請求成功 準備回到主線程重新整理 UI %@",[NSThread currentThread]);
// 主線程重新整理UI
[self performSelectorOnMainThread:@selector(mainThreadRefreshUI) withObject:nil waitUntilDone:YES];
}];
// 主線程重新整理 UI 調用方法
- (void)mainThreadRefreshUI {
NSLog(@"回到了主線程并且重新整理 UI %s : %@", __func__, [NSThread currentThread]);
}
跟我們預想的一樣,網絡請求耗時操作是在子線程中執行,執行結束後調用線程間通信方法回到了主線程重新整理 UI。
以上是關于
NSThread
的介紹和簡單使用的說明,相關 demo 請參考
https://github.com/G-Jayson/Multi-thread