iOS多線程GCD、NSTread和NSOperation簡介

在iOS開發中我們經常會用到多線程來處理一些業務,那麼iOS裏有哪些實現多線程的方式呢?

  • NSTread:封裝程度最小、最輕量級,開銷較大。
  • GCD(Grand Central Dispatch):內部效率優化,提供簡潔的C語言接口,更加簡單高效。
  • NSOperation:基於GCD的一個抽象基類,不需要管理線程的生命週期和同步,比GCD可控性強。
一、NSTread

NSTread封裝程度最小、最輕量級的多線程編程接口,它使用更加靈活,但需要手動管理線程的生命週期、線程同步和線程加鎖等,開銷較大。

/** NSThread 靜態工具方法 **/
    //1、是否開啓了多線程
    BOOL isMultiThread = [NSThread isMultiThreaded];
    //2、獲取當前線程
    NSThread *currentThread = [NSThread currentThread];
    //3、獲取主線程
    NSThread *mainThread = [NSThread mainThread];
    //4、睡眠當前線程
    //4.1、線程睡眠5s
    [NSThread sleepForTimeInterval:5];
    //4.2、線程睡眠到指定時間,效果同上
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]];
    //5、退出當前線程,注意不要在主線程調用,防止主線程被kill掉
    [NSThread exit];

NSThread的使用比較簡單,可以動態創建並初始化NSThread對象,對其進行設置並啓動;也可以通過NSThread的靜態方法快速創建並啓動新線程;

/** NSTread 線程對象的基本創建,target爲入口方法所在的對象,selector爲線程入口方法 **/
    //1、線程實例對象創建與設置
    NSThread *newThread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    //設置線程優先級threadPriority(0~1.0),該屬性即將被拋棄,將使用qualityOfService代替
    //newThread.threadPriority = 1.0;
    newThread.qualityOfService = NSQualityOfServiceUserInteractive;
    //開啓線程
    [newThread start];
    //2、靜態方法快速創建並開啓新線程
    [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
    [NSThread detachNewThreadWithBlock:^{
        NSLog(@"block run");
    }];

此外NSObject提供了隱式快速創建NSThread線程的performSelector系列方法。

/** NSObject 基類隱式創建線程的一些靜態工具方法 **/
    //1、在當前線程上執行方法,延遲2s
    [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
    //2、在指定線程上執行方法,不等待當前線程
    [self performSelector:@selector(run) onThread:newThread withObject:nil waitUntilDone:NO];
    //3、後臺異步執行方法
    [self performSelectorInBackground:@selector(run) withObject:nil];
    //4、在主線程上執行方法
    [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
二、GCD

GCD(Grand Central Dispatch)又叫大中央調度,它對線程操作進行了封裝,加入了很多新的特性,內部進行了效率優化,提供了簡潔的C語言接口,使用更加簡單高效,也是蘋果公司推薦的方式。

GCD的用法特別靈活,在下一篇將詳細講解一下它的用法;這裏主要掌握點分爲以下幾個方面:

1、串行隊列與併發隊列dispatch_queue_t;
/*
     創建隊列
     DISPATCH_QUEUE_SERIAL 表示串行隊列,隊列內任務一個接一個的執行,按照先進先出(FIFO)的順序執行
     DISPATCH_QUEUE_CONCURRENT 表示併發隊列,隊列內任務可同時並列執行,任務之間不會相互等待,執行順序不可預測
     */
    // 串行隊列的創建方法
    dispatch_queue_t queueSerial = dispatch_queue_create("com.jzsec.GCDtest", DISPATCH_QUEUE_SERIAL);
    // 併發隊列的創建方法
    dispatch_queue_t queueCon = dispatch_queue_create("com.jzsec.GCDtest", DISPATCH_QUEUE_CONCURRENT);
2、同步dispatch_sync與異步dispatch_async派發任務;
dispatch_sync(queue, ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
dispatch_async(queue, ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
3、dispatch_once_t 只執行一次;
/**
 * 一次性代碼(只執行一次)dispatch_once
 能保證某段代碼在程序運行過程中只被執行1次,並且即使在多線程的環境下,dispatch_once也可以保證線程安全。
 */
- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只執行1次的代碼(這裏面默認是線程安全的)
        NSLog(@"dispatch_once");
    });
}
4、dispatch_after 延後執行;
/**
 * 延時執行方法 dispatch_after
 dispatch_after函數並不是在指定時間之後纔開始執行處理,而是在指定時間之後將任務追加到主隊列中。嚴格來說,這個時間並不是絕對準確的,但想要大致延遲執行任務,dispatch_after函數是很有效的。
 */
- (void)after {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"asyncMain---begin");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2.0秒後異步追加任務代碼到主隊列,並開始執行
        NSLog(@"after---%@",[NSThread currentThread]);  // 打印當前線程
    });
}
5、dispatch_group_t 組調度;
/**
 * 隊列組 dispatch_group_notify
 當group所有任務都執行完成之後,才執行dispatch_group_notify block 中的任務。
 */
- (void)groupNotify {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的異步任務1、任務2都執行完畢後,回到主線程執行下邊任務
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
        NSLog(@"group---end");
    });
}
三、NSOperation

基於GCD的一個抽象基類,將線程封裝成要執行的操作,不需要管理線程的生命週期和同步,比GCD可控性強,例如加入操作依賴控制操作執行順序、設置操作隊列最大可併發執行的才做個數和取消執行等。

/** NSInvocationOperation 初始化 **/
    NSInvocationOperation *invoOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    [invoOperation start];
    
    /** NSBlockOperation 初始化 **/
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperationA:%@", [NSThread currentThread]);
    }];
    //blockOperation可以後續繼續添加block執行塊,操作執行後會在不同的線程併發執行這些執行塊。
    [blockOperation addExecutionBlock:^{
        NSLog(@"NSBlockOperationB:%@", [NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"NSBlockOperationC:%@", [NSThread currentThread]);
    }];
    [blockOperation start];
    
    /** 獲取主隊列(主線程) **/
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    //創建a、b、c操作
    NSOperation *a = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"OperationA");
    }];
    NSOperation *b = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"OperationB");
    }];
    NSOperation *c = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"OperationC");
    }];
    //添加操作依賴,c依賴於a和b,這樣c一定會在a和b完成之後才執行,即順序爲A、B、C
    [c addDependency:a];
    [c addDependency:b];
    //添加操作a、b、c 到操作隊列queue(特意將c在a和b之前添加)
    [queue addOperation:c];
    [queue addOperation:a];
    [queue addOperation:b];
四、線程同步

對於UI的更新代碼,必須要寫在主線程上執行纔會及時有效;噹噹前代碼不在主線程時,需要將UI更新的部分代碼單獨同步到主線程。
同步的方法有三種:

  • NSThread類的performSelectorOnMainThread方法
  • NSOperation類的mainQueue主隊列
  • GCD的dispatch_get_main_queue()獲取主隊列

推薦直接使用GCD的方法:

dispatch_async(dispatch_get_main_queue( ), ^{
     //刷新UI的代碼
});

在iOS實際開發中,還是使用GCD的情況比較多,這裏只是簡單介紹對了三種多線程實現的方式,在下一篇將詳細介紹一下GCD的用法和注意事項。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章