iOS 多線程(NSOperation使用指南)

NSOperation簡介

NSOperation、NSOperationQueue 是蘋果提供給咱們的一套多線程解決方案。實際上NSOperation、NSOperationQueue 是基於 GCD 更高一層的封裝,徹底面向對象。可是比 GCD 更簡單易用、代碼可讀性也更高bash

爲何要使用 NSOperation、NSOperationQueue?多線程

  • 可添加完成的代碼塊,在操做完成後執行。
  • 添加操做之間的依賴關係,方便的控制執行順序。
  • 設定操做執行的優先級。
  • 能夠很方便的取消一個操做的執行。
  • 使用 KVO 觀察對操做執行狀態的更改:isExecuteing、isFinished、isCancelled。

NSOperation、NSOperationQueue 操做和操做隊列

既然是基於 GCD 的更高一層的封裝。那麼,GCD 中的一些概念一樣適用於 NSOperation、NSOperationQueue。在 NSOperation、NSOperationQueue 中也有相似的任務(操做)和隊列(操做隊列)的概念。併發

  • 操做(Operation
    • 執行操做的意思,換句話說就是你在線程中執行的那段代碼。
    • 在 GCD 中是放在 block 中的。在 NSOperation 中,咱們使用 NSOperation 子類NSInvocationOperation、NSBlockOperation,或者自定義子類來封裝操做。
  • 操做隊列(Operation Queues)
    • 這裏的隊列指操做隊列,即用來存放操做的隊列。不一樣於 GCD 中的調度隊列 FIFO(先進先出)的原則。NSOperationQueue 對於添加到隊列中的操做,首先進入準備就緒的狀態(就緒狀態取決於操做之間的依賴關係),而後進入就緒狀態的操做的開始執行順序(非結束執行順序)由操做之間相對的優先級決定(優先級是操做對象自身的屬性)。
    • 操做隊列經過設置最大併發操做數(maxConcurrentOperationCount)來控制併發、串行。
    • NSOperationQueue 爲咱們提供了兩種不一樣類型的隊列:主隊列和自定義隊列。主隊列運行在主線程之上,而自定義隊列在後臺執行。
  • NSOperation、NSOperationQueue 使用步驟
    • NSOperation 須要配合 NSOperationQueue 來實現多線程。由於默認狀況下,NSOperation 單獨使用時系統同步執行操做,配合 NSOperationQueue 咱們能更好的實現異步執行。
    • NSOperation 實現多線程的使用步驟分爲三步
      • 建立操做:先將須要執行的操做封裝到一個 NSOperation 對象中。
      • 建立隊列:建立 NSOperationQueue 對象。
      • 將操做加入到隊列中:將 NSOperation 對象添加到 NSOperationQueue 對象中。
      • 以後呢,系統就會自動將 NSOperationQueue 中的 NSOperation 取出來,在新線程中執行操做。

NSOperation 和 NSOperationQueue 基本使用

NSOperation 是個抽象類,不能用來封裝操做。咱們只有使用它的子類來封裝操做。咱們有三種方式來封裝操做。框架

  • 使用子類 NSInvocationOperation
  • 使用子類 NSBlockOperation
  • 自定義繼承 NSOperation 的子類,經過實現內部相應的方法來封裝操做。

使用子類 NSInvocationOperation

/**
 NSInvocationOperation : 建立操做 ---> 建立隊列 ---> 操做加入隊列
 */
- (void)demo{
    //1:建立操做
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"123"];
    //2:建立隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //3:操做加入隊列 --- 操做會在新的線程中
    [queue addOperation:op];
}

- (void)handleInvocation:(id)op{
    NSLog(@"%@ --- %@",op,[NSThread currentThread]);
}
複製代碼

打印:異步

2020-01-29 15:43:11.012492+0800 001---NSOperation初體驗[1628:78932] 123 --- <NSThread: 0x600001ea9080>{number = 3, name = (null)}
複製代碼

NSInvocationOperation也能夠手動調起,這樣不會開啓線程,會在當前隊列執行任務ui

/**
 手動調起操做
 */
- (void)demo1{
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"123"];
    // 注意 : 若是該任務已經添加到隊列,你再手動調回出錯
    // NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // [queue addOperation:op];
    // 手動調起操做,默認在當前線程
    [op start];
}

- (void)handleInvocation:(id)op{
    NSLog(@"%@ --- %@",op,[NSThread currentThread]);
}
複製代碼

打印:spa

2020-01-29 15:52:55.404726+0800 001---NSOperation初體驗[1740:83188] 123 --- <NSThread: 0x60000024e940>{number = 1, name = main}
複製代碼

注意:咱們儘可能不要用這種方式去執行任務,若是該任務已經添加到隊列,再手動調起任務程序會崩潰線程

使用子類 NSBlockOperation

/**
 blockOperation 初體驗
 */
- (void)demo2{
 
    //1:建立blockOperation
    NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@ -- %@",[NSThread mainThread],[NSThread currentThread]);
    }];
    //1.1 設置監聽
    bo.completionBlock = ^{
        NSLog(@"完成了!!!");
    };
    //2:建立隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //3:添加到隊列
    [queue addOperation:bo];
}
複製代碼

打印:3d

2020-01-29 15:57:30.760964+0800 001---NSOperation初體驗[1812:85696] <NSThread: 0x600001a66940>{number = 1, name = (null)} -- <NSThread: 0x600001a2dec0>{number = 3, name = (null)}
2020-01-29 15:57:30.761098+0800 001---NSOperation初體驗[1812:85697] 完成了!!!
複製代碼

NSBlockOperation也可使用start方法手動執行,這樣不會開啓線程,會在當前隊列執行任務。若是該任務已經添加到隊列中,手動調起任務程序會崩潰code

自定義繼承 NSOperation 的子類,經過實現內部相應的方法來封裝操做。

若是使用子類 NSInvocationOperation、NSBlockOperation 不能知足平常需求,咱們可使用自定義繼承自 NSOperation 的子類。

先定義一個繼承 NSOperation 的子類,重寫main方法。

// NHOperation.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NHOperation : NSOperation

@end

NS_ASSUME_NONNULL_END

// NHOperation.m
#import "NHOperation.h"

@implementation NHOperation

- (void)main{
    
    if (!self.isCancelled) {
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@", [NSThread currentThread]);
        }
    }
}
@end
複製代碼

導入頭文件NHOperation.h

- (void)demo3{
    NHOperation *nhQueue = [[NHOperation alloc]init];
    [nhQueue start];
}
複製代碼

打印:

2020-01-29 16:20:57.036713+0800 001---NSOperation初體驗[2060:94891] 1---<NSThread: 0x600001c16940>{number = 1, name = main}
2020-01-29 16:20:59.037879+0800 001---NSOperation初體驗[2060:94891] 1---<NSThread: 0x600001c16940>{number = 1, name = main}
複製代碼

這種自定義繼承NSOperation的寫法能夠本身掌握線程的生命週期,SDWebImage框架重寫了start,main等一系列方法,去實現下載圖片模塊

可執行代碼塊

/**
 可執行代碼塊
 */
- (void)demo5{
    //建立操做
    NSBlockOperation *ob = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    //添加執行代碼塊
    [ob addExecutionBlock:^{
        NSLog(@"這是一個執行代碼塊 - %@",[NSThread currentThread]);
    }];
    // 執行代碼塊 在新的線程  建立的操做在當前線程
    //[ob start];
    
    //利用隊列,兩個都在新的線程中
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:ob];
    
}
複製代碼

打印:

2020-01-29 16:37:49.565971+0800 001---NSOperation初體驗[2207:101558] <NSThread: 0x600002457d40>{number = 3, name = (null)}
2020-01-29 16:37:49.565991+0800 001---NSOperation初體驗[2207:101555] 這是一個執行代碼塊 - <NSThread: 0x600002458480>{number = 4, name = (null)}
複製代碼

線程間通信

/**
 線程通信
 */
- (void)demo6{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"Noah";
    [queue addOperationWithBlock:^{
        NSLog(@"%@ = %@",[NSOperationQueue currentQueue],[NSThread currentThread]);
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"%@ --%@",[NSOperationQueue currentQueue],[NSThread currentThread]);
        }];
    }];
}
複製代碼

打印:

2020-01-29 16:38:41.372075+0800 001---NSOperation初體驗[2239:102618] <NSOperationQueue: 0x6000015ee560>{name = 'Noah'} = <NSThread: 0x6000000f9880>{number = 3, name = (null)}
2020-01-29 16:38:41.378217+0800 001---NSOperation初體驗[2239:102581] <NSOperationQueue: 0x6000015ea180>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600000086940>{number = 1, name = main}
複製代碼

優先級設定

/**
 優先級,只會讓CPU有更高的概率調用,不是說設置高就必定所有先完成
 */
- (void)demo4{
    NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"第1個操做 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    // 設置優先級 - 最高
    bo1.qualityOfService = NSQualityOfServiceUserInteractive;
    
    //建立第二個操做
    NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"第二個操做 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    // 設置優先級 - 最低
    bo2.qualityOfService = NSQualityOfServiceBackground;
    
        //2:建立隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //3:添加到隊列
    [queue addOperation:bo1];
    [queue addOperation:bo2];
}
複製代碼

打印:

2020-01-29 16:42:21.830886+0800 001---NSOperation初體驗[2296:104794] 第1個操做 0 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.830901+0800 001---NSOperation初體驗[2296:104795] 第2個操做 0 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831015+0800 001---NSOperation初體驗[2296:104794] 第1個操做 1 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831076+0800 001---NSOperation初體驗[2296:104794] 第1個操做 2 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831107+0800 001---NSOperation初體驗[2296:104795] 第2個操做 1 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831135+0800 001---NSOperation初體驗[2296:104794] 第1個操做 3 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831217+0800 001---NSOperation初體驗[2296:104794] 第1個操做 4 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831241+0800 001---NSOperation初體驗[2296:104795] 第2個操做 2 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831306+0800 001---NSOperation初體驗[2296:104794] 第1個操做 5 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831351+0800 001---NSOperation初體驗[2296:104795] 第2個操做 3 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831387+0800 001---NSOperation初體驗[2296:104794] 第1個操做 6 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831438+0800 001---NSOperation初體驗[2296:104795] 第2個操做 4 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831595+0800 001---NSOperation初體驗[2296:104794] 第1個操做 7 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831771+0800 001---NSOperation初體驗[2296:104794] 第1個操做 8 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831957+0800 001---NSOperation初體驗[2296:104795] 第2個操做 5 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.834469+0800 001---NSOperation初體驗[2296:104794] 第1個操做 9 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.834487+0800 001---NSOperation初體驗[2296:104795] 第2個操做 6 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.834579+0800 001---NSOperation初體驗[2296:104795] 第2個操做 7 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.834659+0800 001---NSOperation初體驗[2296:104795] 第2個操做 8 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.834761+0800 001---NSOperation初體驗[2296:104795] 第2個操做 9 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
複製代碼

設置隊列最大併發數

- (void)demo7{
    
    NSOperationQueue *queue = [NSOperationQueue new];
    // 設置隊列最大併發數爲2
    queue.maxConcurrentOperationCount = 2;
    for (int i = 0; i<10; i++) {
        [queue addOperationWithBlock:^{
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        }];
    }
}
複製代碼

打印:

2020-01-29 16:45:11.512510+0800 001---NSOperation初體驗[2360:107175] 0-<NSThread: 0x6000023c2140>{number = 3, name = (null)}
2020-01-29 16:45:11.512505+0800 001---NSOperation初體驗[2360:107173] 1-<NSThread: 0x6000023d1280>{number = 4, name = (null)}
2020-01-29 16:45:13.514845+0800 001---NSOperation初體驗[2360:107172] 2-<NSThread: 0x6000023c4980>{number = 5, name = (null)}
2020-01-29 16:45:13.514883+0800 001---NSOperation初體驗[2360:107174] 3-<NSThread: 0x6000023fc380>{number = 6, name = (null)}
2020-01-29 16:45:15.516537+0800 001---NSOperation初體驗[2360:107173] 4-<NSThread: 0x6000023d1280>{number = 4, name = (null)}
2020-01-29 16:45:15.516537+0800 001---NSOperation初體驗[2360:107172] 5-<NSThread: 0x6000023c4980>{number = 5, name = (null)}
2020-01-29 16:45:17.518188+0800 001---NSOperation初體驗[2360:107173] 7-<NSThread: 0x6000023d1280>{number = 4, name = (null)}
2020-01-29 16:45:17.518188+0800 001---NSOperation初體驗[2360:107174] 6-<NSThread: 0x6000023fc380>{number = 6, name = (null)}
2020-01-29 16:45:19.521176+0800 001---NSOperation初體驗[2360:107172] 8-<NSThread: 0x6000023c4980>{number = 5, name = (null)}
2020-01-29 16:45:19.521176+0800 001---NSOperation初體驗[2360:107174] 9-<NSThread: 0x6000023fc380>{number = 6, name = (null)}
複製代碼

任務相互之間的依賴關係

- (void)demo{
    NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"請求token");
    }];
    
    NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿着token,請求數據1");
    }];
    
    NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿着數據1,請求數據2");
    }];
    
    //由於異步,很差控制,咱們藉助依賴
    [bo2 addDependency:bo1];
    [bo3 addDependency:bo2];
    //注意這裏必定不要構成循環依賴 : 不會報錯,可是全部操做不會執行
    //[bo1 addDependency:bo3];
    //waitUntilFinished 堵塞線程
    [self.queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:NO];
    
    NSLog(@"執行完了?我要幹其餘事");
}
複製代碼

打印:

2020-01-29 16:47:41.371621+0800 002---NSOperation深刻淺出[2422:109106] 執行完了?我要幹其餘事
2020-01-29 16:47:41.872960+0800 002---NSOperation深刻淺出[2422:109157] 請求token
2020-01-29 16:47:42.374790+0800 002---NSOperation深刻淺出[2422:109156] 拿着token,請求數據1
2020-01-29 16:47:42.877750+0800 002---NSOperation深刻淺出[2422:109156] 拿着數據1,請求數據2
複製代碼

NSOperationQueue的掛起,繼續,取消

/**
 關於operationQueue的掛起,繼續,取消
 */
- (void)demo1{
    self.queue.name = @"Cooci";
    self.queue.maxConcurrentOperationCount = 2;
    for (int i=0; i< 20; i++) {
        [self.queue addOperationWithBlock:^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"%@-----%d",[NSThread currentThread],i);
        }];
    }
    
}

- (IBAction)pauseOrContinue:(id)sender {
    if (self.queue.operationCount == 0) {
        NSLog(@"當前沒有操做,沒有必要掛起和繼續");
        return;
    }
    // 一個細節 正在執行的操做沒法掛起
    if (self.queue.suspended) {
        NSLog(@"當前是掛起狀態,準備繼續");
    }else{
        NSLog(@"當前爲執行狀態,準備掛起");
    }
    self.queue.suspended = !self.queue.isSuspended;
    
}
- (IBAction)cancel:(id)sender {
    [self.queue cancelAllOperations];
}
複製代碼

打印:

2020-01-29 16:52:44.854415+0800 002---NSOperation深刻淺出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----1
2020-01-29 16:52:44.854415+0800 002---NSOperation深刻淺出[2488:111998] <NSThread: 0x600001d1da40>{number = 4, name = (null)}-----0
2020-01-29 16:52:45.856950+0800 002---NSOperation深刻淺出[2488:111999] <NSThread: 0x600001d1f080>{number = 5, name = (null)}-----2
2020-01-29 16:52:45.856988+0800 002---NSOperation深刻淺出[2488:112000] <NSThread: 0x600001d04280>{number = 6, name = (null)}-----3
2020-01-29 16:52:46.277210+0800 002---NSOperation深刻淺出[2488:111955] 當前爲執行狀態,準備掛起
2020-01-29 16:52:46.857767+0800 002---NSOperation深刻淺出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----4
2020-01-29 16:52:46.857782+0800 002---NSOperation深刻淺出[2488:111999] <NSThread: 0x600001d1f080>{number = 5, name = (null)}-----5
2020-01-29 16:52:48.967113+0800 002---NSOperation深刻淺出[2488:111955] 當前是掛起狀態,準備繼續
2020-01-29 16:52:49.968072+0800 002---NSOperation深刻淺出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----6
2020-01-29 16:52:49.968087+0800 002---NSOperation深刻淺出[2488:111999] <NSThread: 0x600001d1f080>{number = 5, name = (null)}-----7
2020-01-29 16:52:50.969946+0800 002---NSOperation深刻淺出[2488:112000] <NSThread: 0x600001d04280>{number = 6, name = (null)}-----8
2020-01-29 16:52:50.969946+0800 002---NSOperation深刻淺出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----9
2020-01-29 16:52:51.465420+0800 002---NSOperation深刻淺出[2488:111955] 所有取消
2020-01-29 16:52:51.973286+0800 002---NSOperation深刻淺出[2488:111999] <NSThread: 0x600001d1f080>{number = 5, name = (null)}-----10
2020-01-29 16:52:51.973297+0800 002---NSOperation深刻淺出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----11

複製代碼

參考文章

iOS 多線程:『NSOperation、NSOperationQueue』詳盡總結

相關文章
相關標籤/搜索