天天看點

iOS:多線程(二) —— NSThread

前言

在日常開發中,多線程的使用能幫助我們解決很多問題,比如大量資料的運算,複雜程式的執行,以及利用鎖來實作一些需求,本系列文章主要介紹 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]);
}
           
iOS:多線程(二) —— NSThread

跟我們預想的一樣,網絡請求耗時操作是在子線程中執行,執行結束後調用線程間通信方法回到了主線程重新整理 UI。

以上是關于

NSThread

的介紹和簡單使用的說明,相關 demo 請參考

https://github.com/G-Jayson/Multi-thread