iOS線程之NSOperation

前篇文章已經講了GCD了,那么這兩者有什么區別?

GCD VS NSOperation

"NSOperationQueue predates Grand Central Dispatch and on iOS it doesn't use GCD to execute operations (this is different on Mac OS X). It uses regular background threads which have a little more overhead than GCD dispatch queues.
On the other hand, NSOperationQueue gives you a lot more control over how your operations are executed. You can define dependencies between individual operations for example, which isn't possible with plain GCD queues. It is also possible to cancel operations that have been enqueued in an NSOperationQueue (as far as the operations support it). When you enqueue a block in a GCD dispatch queue, it will definitely be executed at some point.
To sum it up, NSOperationQueue can be more suitable for long-running operations that may need to be cancelled or have complex dependencies. GCD dispatch queues are better for short tasks that should have minimum performance and memory overhead."

簡單來說就是GCD偏底層點,性能好,依賴關系少,并發耗費資源少。
NSOperation可觀察狀態,性能也不錯,處理事務更簡單操作。

對于這兩種都熟練運用的人來說,無所謂了,APP大多數事務這兩者都能完美解決。至于代碼用哪個這個取決于你的興趣了。

下面詳細說一下NSOperation

@interface NSOperation : NSObject {

- (void)start; //開始執行 默認是同步執行的
- (void)main; //主任務的函數 

@property (readonly, getter=isCancelled) BOOL cancelled; //是否取消
- (void)cancel; //取消任務

@property (readonly, getter=isExecuting) BOOL executing;//是否正在執行
@property (readonly, getter=isFinished) BOOL finished; //是否完成
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below 是否并行
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0); //是否異步
@property (readonly, getter=isReady) BOOL ready; //是否正在等待

- (void)addDependency:(NSOperation *)op; //添加依賴
- (void)removeDependency:(NSOperation *)op; //刪除依賴關系

@property (readonly, copy) NSArray<NSOperation *> *dependencies; //所有依賴關系的數組

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
//隊列優先級  優先級高的先執行 一般設置為0 即 NSOperationQueuePriorityNormal。
 NSOperationQueuePriorityVeryLow = -8L,
 NSOperationQueuePriorityLow = -4L,
 NSOperationQueuePriorityNormal = 0,
 NSOperationQueuePriorityHigh = 4,
 NSOperationQueuePriorityVeryHigh = 8
};

@property NSOperationQueuePriority queuePriority;//隊列優先級

@property (nullable, copy) void (^completionBlock)(void)  NS_AVAILABLE(10_6, 4_0);//完成時候執行的代碼塊
//等待直到完成
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);
//線程優先級
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);

@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
NSQualityOfService 的幾個枚舉值:
  NSQualityOfServiceUserInteractive:最高優先級,主要用于提供交互UI的操作,比如處理點擊事件,繪制圖像到屏幕上
  NSQualityOfServiceUserInitiated:次高優先級,主要用于執行需要立即返回的任務
  NSQualityOfServiceDefault:默認優先級,當沒有設置優先級的時候,線程默認優先級
  NSQualityOfServiceUtility:普通優先級,主要用于不需要立即返回的任務
  NSQualityOfServiceBackground:后臺優先級,用于完全不緊急的任務


//名字
@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);

NSBlockOperation

- (void)print{
    NSLog(@"線程info : %@",[NSThread currentThread]);
}
- (void)test4{
    NSBlockOperation * blop = [[NSBlockOperation alloc]init];
    [blop addExecutionBlock:^{//添加同時執行的task
        NSLog(@"1 start");
        [self print];
        sleep(2);
        
        NSLog(@"1 end");
    }];
    [blop addExecutionBlock:^{ //添加同時執行的task
        NSLog(@"2 start");
        [self print];
        sleep(4);
        
        NSLog(@"2 end");
    }];
    [blop addExecutionBlock:^{ //添加同時執行的task
        NSLog(@"3 start");
        [self print];
        sleep(1);
        
        NSLog(@"3 end");
    }];
    [blop setCompletionBlock:^{ //添加同時執行的task
        NSLog(@"blop end");
    }];
    
    [blop start];
}
輸出:
**2016-03-29 16:47:44.857 GCD_Demo[17555:562249] 1 start**
**2016-03-29 16:47:44.857 GCD_Demo[17555:562287] 3 start**
**2016-03-29 16:47:44.857 GCD_Demo[17555:562288] 2 start**
**2016-03-29 16:47:44.857 GCD_Demo[17555:562249] ****線程****info : <NSThread: 0x7fea68408b30>{number = 1, name = main}**
**2016-03-29 16:47:44.857 GCD_Demo[17555:562288] ****線程****info : <NSThread: 0x7fea6861fb30>{number = 3, name = (null)}**
**2016-03-29 16:47:44.857 GCD_Demo[17555:562287] ****線程****info : <NSThread: 0x7fea69300470>{number = 2, name = (null)}**
**2016-03-29 16:47:45.922 GCD_Demo[17555:562287] 3 end**
**2016-03-29 16:47:46.858 GCD_Demo[17555:562249] 1 end**
**2016-03-29 16:47:48.928 GCD_Demo[17555:562288] 2 end**
**2016-03-29 16:47:48.929 GCD_Demo[17555:562288] blop end**

可以看出來,NSBlockOperation當任務是1的時候在main線程中執行,任務大于1的時候,其他的個自獨自開了線程,而且互不影響。

依賴關系

- (void)print{
    NSLog(@"線程info : %@",[NSThread currentThread]);
}
- (void)test4{
    NSBlockOperation * blop = [[NSBlockOperation alloc]init];
    [blop addExecutionBlock:^{
        NSLog(@"blop1_1 start");
        [self print];
        sleep(2);
        NSLog(@"blop1_1 end");
    }];
    [blop addExecutionBlock:^{
        NSLog(@"blop1_2 start");
        [self print];
        sleep(4);
        NSLog(@"blop1_2 end");
    }];
    NSLog(@"blop will start");
    [blop start];
    NSLog(@"blop did start");
    
    NSBlockOperation * blop2 =[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"blop2 start");
        [self print];
        sleep(2);
        NSLog(@"blop2 end");
    }];
   // [blop2 addDependency:blop];//blop2 依賴blop 就是blopExecutionBlock 執行完之后再執行blop2的任務【blop2 執行task和blop 的CompletionBlock基本是同時執行的】
    [blop2 start];
輸出:
**2016-03-29 17:06:53.217 GCD_Demo[17806:574416] blop will start**
**2016-03-29 17:06:53.217 GCD_Demo[17806:574416] blop1_1 start**
**2016-03-29 17:06:53.217 GCD_Demo[17806:574455] blop1_2 start**
**2016-03-29 17:06:53.217 GCD_Demo[17806:574416] ****線程****info : <NSThread: 0x7f839a004ff0>{number = 1, name = main}**
**2016-03-29 17:06:53.218 GCD_Demo[17806:574455] ****線程****info : <NSThread: 0x7f8398416d80>{number = 2, name = (null)}**
**2016-03-29 17:06:55.219 GCD_Demo[17806:574416] blop1_1 end**
**2016-03-29 17:06:57.272 GCD_Demo[17806:574455] blop1_2 end**
**2016-03-29 17:06:57.272 GCD_Demo[17806:574416] blop did start**
**2016-03-29 17:06:57.273 GCD_Demo[17806:574416] blop2 start**
**2016-03-29 17:06:57.273 GCD_Demo[17806:574416] ****線程****info : <NSThread: 0x7f839a004ff0>{number = 1, name = main}**
**2016-03-29 17:06:59.274 GCD_Demo[17806:574416] blop2 end**

從輸出的信息可以看出來,block是同步執行的,雖然多任務是多線程,但是主線程還是在阻塞中,只有上一個所有 task 執行完的時候,才會執行下邊的task。所以在這里依賴關系不那么重要了,注釋掉運行結果也一樣的。

NSInvocationOperation

NSInvocationOperation 是NSOperation的子類,負責實現operation的SEL方法。
這樣子operation就可以start的時候執行一些函數了。
在swift中已經廢棄
看文檔:
NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available")

NSOperationQueue

//添加操作
- (void)addOperation:(NSOperation *)op;
//添加操作數組 在完成操作的時候
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);
//添加攜帶代碼塊的operation
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);
//所有的操作 組成的數組 可讀屬性
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
//操作個數
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);
//設置最大并行的任務數 ps:operation 其實 一個operation可以同時開啟幾個線程的。
@property NSInteger maxConcurrentOperationCount;
//掛起
@property (getter=isSuspended) BOOL suspended;
//隊列的名字
@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);
//優先級
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
隊列
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);
//取消所有的操作
- (void)cancelAllOperations;
//等到他們的操作結束
- (void)waitUntilAllOperationsAreFinished;
//當前的隊列
+ (nullable NSOperationQueue *)currentQueue NS_AVAILABLE(10_6, 4_0);
//主隊列
+ (NSOperationQueue *)mainQueue NS_AVAILABLE(10_6, 4_0);

 
# 隊列的例子
#隊列中添加的operation都是在子線程中執行的。
- (void)print{
    NSLog(@"線程info : %@",[NSThread currentThread]);
}
- (void)op1{
     NSLog(@"op1 開始運行了");
     sleep(3);
     NSLog(@"op1 結束");
}

- (void)test5{
    NSInvocationOperation * op1 =[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(op1) object:nil];
    NSOperationQueue * queue =[[NSOperationQueue alloc]init];
    [queue addOperation:op1]; //添加操作
    queue.maxConcurrentOperationCount = 1;//同時允許一個operation運行
    NSBlockOperation *block = [self test4];//任務塊
    [queue addOperation:block];//添加任務塊并運行

// sleep(2);  
   // [queue cancelAllOperations];
}
- (NSBlockOperation *)test4{
    NSBlockOperation * blop = [[NSBlockOperation alloc]init];
    [blop addExecutionBlock:^{
        NSLog(@"blop1_1 start");
        [self print];
        sleep(2);
        NSLog(@"blop1_1 end");
    }];
    [blop addExecutionBlock:^{
        NSLog(@"blop1_2 start");
        [self print];
        sleep(4);
        NSLog(@"blop1_2 end");
    }];
    return blop;
}
輸出:
**2016-03-31 11:22:16.663 GCD_Demo[26038:889212] op1 ****開始運行了**
**2016-03-31 11:22:19.737 GCD_Demo[26038:889212] op1 ****結束**
**2016-03-31 11:22:19.738 GCD_Demo[26038:889213] blop1_1 start**
**2016-03-31 11:22:19.738 GCD_Demo[26038:889226] blop1_2 start**
**2016-03-31 11:22:19.738 GCD_Demo[26038:889213] ****線程****info : <NSThread: 0x7fea3061d110>{number = 2, name = (null)}**
**2016-03-31 11:22:19.738 GCD_Demo[26038:889226] ****線程****info : <NSThread: 0x7fea31800140>{number = 3, name = (null)}**
**2016-03-31 11:22:21.808 GCD_Demo[26038:889213] blop1_1 end**
**2016-03-31 11:22:23.784 GCD_Demo[26038:889226] blop1_2 end**
# 從輸出的信息可以看出來,當設置最大的operation為1的時候,相當于這個隊列同步運行了,不過這個同步的單位不是線程,而是operation。

當把這兩句代碼加到 test5最后邊輸出結果是:
**2016-03-31 11:28:59.267 GCD_Demo[26113:892737] op1 ****開始運行了**
**2016-03-31 11:29:02.341 GCD_Demo[26113:892737] op1 ****結束**
從輸出結果得出:正在執行的Operation無法stop,正在ready的operation直接跳過start,執行complateBlock.狀態由ready改為canceld。ps:注意看官方文檔
`Canceling the operations does not automatically remove them from the queue or stop those that are currently executing.`
正在執行的不會從隊列中刪除也不會stop。
`For operations that are queued and waiting execution, the queue must still attempt to execute the operation before recognizing that it is canceled and moving it to the finished state. 
For operations that are already executing, the operation object itself must check for cancellation and stop what it is doing so that it can move to the finished state. 
In both cases, a finished (or canceled) operation is still given a chance to execute its completion block before it is removed from the queue.` 
正在隊列中等待的operation執行的時候會檢測是否被cancenld,如果狀態是canceld,那么直接執行completion block 在它被隊列刪除的時候。

在子線程中耗時的操作完成了,那么該在主線程中更新UI

#將上面的test5 改成下面的代碼
- (void)test5{
    NSInvocationOperation * op1 =[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(op1) object:nil];
    NSOperationQueue * queue =[[NSOperationQueue alloc]init];
    [queue addOperation:op1];
    queue.maxConcurrentOperationCount = 3;//根據需要設置數量
    NSBlockOperation *block = [self test4];
    [queue addOperation:block];
//這句話一定要添加,這句話的意思等到所有的operation都完成了在執行后面的代碼,
其實就是上面的操作執行到這里要等待他們直到他們都完成了。
#     [queue waitUntilAllOperationsAreFinished]; 
    NSBlockOperation * blockUpdateMainUI=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"update UI");
    }];
    [[NSOperationQueue mainQueue] addOperation:blockUpdateMainUI];//在主隊列中執行更新UI的操作
}
上面的代碼 和GCD中的分組有些類似,但是 這個OperationQueue基本單位是operation而不是線程,一定要理解。
operation和線程的關系是 一個operation可能對應多個線程,也可能對應一個線程。

關于NSOperationQueue的了解和使用我想到的基本就這么多場景,后期有其他的場景再補充。
預告:下期節目是NSThread的介紹和使用。
ps:廣告時間


更多文章:www.fgyong.cn
有問題可以發我郵箱討論共同交流技術。
fgyong@yeah.net

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 現在iOS的多線程方案主要有以下這幾種: GCD(Grand Central Dispatch):使用dispat...
    寒光冷劍閱讀 1,679評論 0 2
  • 一、前言 上一篇文章iOS多線程淺匯-原理篇中整理了一些有關多線程的基本概念。本篇博文介紹的是iOS中常用的幾個多...
    nuclear閱讀 2,073評論 6 18
  • iOS Concurrency Programming Guide iOS 和 Mac OS 傳統的并發編程模型是...
    YangPu閱讀 858評論 0 2
  • 簡介 在iOS中,我們需要將非UI且耗時的任務放在主線程當中執行,同時確保在任務完成時進行回調。常用的三種實現多線...
    adduct閱讀 397評論 0 1
  • Object C中創建線程的方法是什么?如果在主線程中執行代碼,方法是什么?如果想延時執行代碼、方法又是什么? 1...
    AlanGe閱讀 1,793評論 0 17