iOS-多線程(五)NSOperation NSOperationQueue

NSOperation類是iOS2.0推出的拼坎,從OS X10.6和iOS4推出GCD時泰鸡,又重寫了NSOperation和NSOperationQueue,NSOperation和NSOperationQueue分別對應GCD的任務和隊列余舶,NSOperation赠制、NSOperationQueue 是基于 GCD 更高一層的封裝,完全面向對象褥符。比 GCD 更加靈活喷楣,功能也更加強大。

1. Operation Queues(NSOperation铣焊,NSOperationQueue) vs. Grand Central Dispatch (GCD)

  • Operation Queues :相對 GCD 來說,使用 Operation Queues 會增加一點點額外的開銷叽讳,但是我們卻換來了非常強大的靈活性和功能,我們可以給 operation 之間添加依賴關系岛蚤、取消一個正在執(zhí)行的 operation 、暫停和恢復 operation queue 等懈糯;

  • GCD :則是一種更輕量級的涤妒,以 FIFO 的順序執(zhí)行并發(fā)任務的方式赚哗,使用 GCD 時我們并不關心任務的調度情況贿讹,而讓系統(tǒng)幫我們自動處理。但是 GCD 的短板也是非常明顯的,比如我們想要給任務之間添加依賴關系雹食、取消或者暫停一個正在執(zhí)行的任務時就會變得非常棘手畜普;

2. NSOperation,NSOperationQueue

  • NSOperation就是對任務進行的封裝群叶。它本身是一個抽象類吃挑,不能直接實例化。如果我們想要使用它來執(zhí)行具體任務街立,就必須創(chuàng)建自己的子類(繼承NSOperation)或者直接使用系統(tǒng)提供的兩個子類:NSInvocationOperation 和 NSBlockOperation舶衬;

  • NSOperationQueue就是任務的執(zhí)行隊列,可進行串行隊列的執(zhí)行或并發(fā)隊列的執(zhí)行赎离;

3. NSOperation開啟的兩種方式

  • 第一種:直接由NSOperation子類對象啟動逛犹。 首先將需要執(zhí)行的操作封裝到NSOperation子類對象中,然后該對象調用start方法梁剔。直接使用start方法虽画,那么操作會在當前線程中同步執(zhí)行,不會創(chuàng)建新線程荣病。

  • 第二種:NSOperation對象添加到NSOperationQueue對象中码撰,由該隊列對象啟動操作,操作會異步執(zhí)行:
    (1)創(chuàng)建操作:先將需要執(zhí)行的操作封裝到一個 NSOperation 對象中个盆;
    (2)創(chuàng)建隊列:創(chuàng)建 NSOperationQueue 對象脖岛;
    (3)將操作加入到隊列中:將 NSOperation 對象添加到 NSOperationQueue 對象中;
    系統(tǒng)自動從隊列中取出線程砾省,并且自動放到線程中執(zhí)行鸡岗。

4. NSOperation和NSOperationQueue的基本使用

4.1 NSInvocationOperation簡單使用
  • 不通過NSOperationQueue
//NSInvocationOperation  同步執(zhí)行
- (void)invocationOperationSync
{
    
    //創(chuàng)建 operation
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"任務1"];
    
    [operation start];
    
}

- (void)task:(NSString *)taskName {
    for (NSInteger i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"%@---%@", taskName, [NSThread currentThread]);
    }
}

打印結果:

2019-07-20 20:18:15.101039+0800 NSOperationDemo[4388:112069] 任務1---<NSThread: 0x6000026d9380>{number = 1, name = main}
2019-07-20 20:18:16.102574+0800 NSOperationDemo[4388:112069] 任務1---<NSThread: 0x6000026d9380>{number = 1, name = main}

在沒有使用 NSOperationQueue,在主線程中單獨使用使用子類 NSInvocationOperation 執(zhí)行一個操作的情況下编兄,操作是在當前線程執(zhí)行的轩性,并沒有開啟新線程。

  • 通過NSOperationQueue
//NSInvocationOperation  異步執(zhí)行
- (void)invocationOperationAsync
{
    //創(chuàng)建 operation
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"任務1"];
    //創(chuàng)建一個操作隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //將任務添加到隊列中
    [queue addOperation:operation];
}

打印結果:

2019-07-20 20:52:31.394786+0800 NSOperationDemo[4838:122958] 任務1---<NSThread: 0x60000321dcc0>{number = 3, name = (null)}
2019-07-20 20:52:32.396118+0800 NSOperationDemo[4838:122958] 任務1---<NSThread: 0x60000321dcc0>{number = 3, name = (null)}

使用 NSOperationQueue狠鸳,操作會異步并發(fā)執(zhí)行揣苏,會開辟新線程。

4.2 NSBlockOperation簡單使用
  • 不通過NSOperationQueue
//NSBlockOperation 同步執(zhí)行
- (void)blockOperationSync
{
    //創(chuàng)建 operation
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"block operation---%@", [NSThread currentThread]);
        }
    }];
    
    [operation start];
}

打印結果:

2019-07-20 21:48:56.673690+0800 NSOperationDemo[5561:142065] block operation---<NSThread: 0x60000018e980>{number = 1, name = main}
2019-07-20 21:48:57.675189+0800 NSOperationDemo[5561:142065] block operation---<NSThread: 0x60000018e980>{number = 1, name = main}

同樣在沒有使用 NSOperationQueue件舵,不會開啟新的線程卸察。

  • 通過NSOperationQueue
//NSBlockOperation 異步執(zhí)行
- (void)blockOperationAsync
{
    //創(chuàng)建 operation
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"block operation---%@", [NSThread currentThread]);
        }
    }];
    
    //創(chuàng)建一個操作隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //將任務添加到隊列中
    [queue addOperation:operation];
}

打印結果:

2019-07-20 21:54:58.094872+0800 NSOperationDemo[5650:144283] block operation---<NSThread: 0x600002ab1540>{number = 3, name = (null)}
2019-07-20 21:54:59.099257+0800 NSOperationDemo[5650:144283] block operation---<NSThread: 0x600002ab1540>{number = 3, name = (null)}

使用 NSOperationQueue,操作會異步并發(fā)執(zhí)行铅祸,會開辟新線程坑质。

另外合武,NSBlockOperation 還提供了一個方法。

- (void)addExecutionBlock:(void (^)(void))block;

這個方法可以為 NSBlockOperation 添加額外的操作涡扼。每個操作可以異步并發(fā)執(zhí)行稼跳,所有相關的操作執(zhí)行完成時,整個操作才算完成吃沪。
簡單示例:

//NSBlockOperation addExecutionBlock
- (void)blockOperationAddExecutionBlock
{
    //創(chuàng)建 operation
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任務1:---%@", [NSThread currentThread]);
        }
    }];
    
    for (NSInteger i = 2; i < 7; ++i) {
        [operation addExecutionBlock:^{
            for (NSInteger j = 0; j < 2; ++j) {
                [NSThread sleepForTimeInterval:1.0];
                NSLog(@"任務%ld:---%@", i, [NSThread currentThread]);
            }
        }];
    }
    
    [operation start];
}

打印結果:

2019-07-22 10:43:51.309602+0800 NSOperationDemo[2028:55710] 任務4:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:51.309602+0800 NSOperationDemo[2028:55757] 任務1:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}
2019-07-22 10:43:51.309602+0800 NSOperationDemo[2028:55755] 任務2:---<NSThread: 0x600000e748c0>{number = 5, name = (null)}
2019-07-22 10:43:51.309611+0800 NSOperationDemo[2028:55758] 任務3:---<NSThread: 0x600000e71400>{number = 4, name = (null)}
2019-07-22 10:43:52.310394+0800 NSOperationDemo[2028:55757] 任務1:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}
2019-07-22 10:43:52.310441+0800 NSOperationDemo[2028:55758] 任務3:---<NSThread: 0x600000e71400>{number = 4, name = (null)}
2019-07-22 10:43:52.310392+0800 NSOperationDemo[2028:55710] 任務4:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:52.310452+0800 NSOperationDemo[2028:55755] 任務2:---<NSThread: 0x600000e748c0>{number = 5, name = (null)}
2019-07-22 10:43:53.311787+0800 NSOperationDemo[2028:55757] 任務5:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}
2019-07-22 10:43:53.311787+0800 NSOperationDemo[2028:55710] 任務6:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:54.313078+0800 NSOperationDemo[2028:55710] 任務6:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:54.313078+0800 NSOperationDemo[2028:55757] 任務5:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}

從打印結果可以看出汤善,任務1的操作沒有在主線程執(zhí)行,任務4的操作在主線程執(zhí)行票彪,得出:

添加的操作多的話红淡,blockOperationWithBlock: 中的操作也可能會在其他線程(非當前線程)中執(zhí)行,這是由系統(tǒng)決定的降铸,并不是說添加到 blockOperationWithBlock: 中的操作一定會在當前線程中執(zhí)行在旱。同樣addExecutionBlock: 也是一樣,在哪個線程執(zhí)行是系統(tǒng)決定垮耳。

4.3 自定義NSOperation

當系統(tǒng)預定義的兩個子類 NSInvocationOperation 和 NSBlockOperation 不能很好的滿足我們的需求時颈渊,我們可以自定義自己的 NSOperation 子類,添加我們想要的功能终佛。

可以通過重寫 main 或者 start 方法 來定義自己的 NSOperation 對象。重寫main方法比較簡單雾家,我們不需要管理操作的狀態(tài)屬性 isExecuting 和 isFinished铃彰。當 main 執(zhí)行完返回的時候,操作就結束(isFinished為YES)芯咧。

簡單示例:

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

NS_ASSUME_NONNULL_BEGIN

@interface NonConcurrentOperation : NSOperation

@end

NS_ASSUME_NONNULL_END

//NonConcurrentOperation.m
#import "NonConcurrentOperation.h"

@implementation NonConcurrentOperation

- (void)main
{
    for (NSInteger i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"任務牙捉,---%@", [NSThread currentThread]);
    }
}

@end

使用NonConcurrentOperation類

- (void)nonConcurrentOperationAction
{
    //不加入隊列
    NonConcurrentOperation *operation = [[NonConcurrentOperation alloc] init];
    [operation start];
    
    NSLog(@"---------");
    
    //加入隊列
    NonConcurrentOperation *operation1 = [[NonConcurrentOperation alloc] init];
    NSOperationQueue *queue = [NSOperationQueue new];
    [queue addOperation:operation1];
}

打印結果:

2019-07-22 15:02:22.924678+0800 NSOperationDemo[4590:129265] 任務,---<NSThread: 0x600000eda900>{number = 1, name = main}
2019-07-22 15:02:23.926285+0800 NSOperationDemo[4590:129265] 任務敬飒,---<NSThread: 0x600000eda900>{number = 1, name = main}
2019-07-22 15:02:23.926549+0800 NSOperationDemo[4590:129265] ---------
2019-07-22 15:02:24.932274+0800 NSOperationDemo[4590:129326] 任務邪铲,---<NSThread: 0x600000eb6380>{number = 3, name = (null)}
2019-07-22 15:02:25.934638+0800 NSOperationDemo[4590:129326] 任務,---<NSThread: 0x600000eb6380>{number = 3, name = (null)}

在默認情況下无拗,operation 是同步執(zhí)行的带到,也就是說在調用它的 start 方法的線程中執(zhí)行它們的任務。而在 operation 和 operation queue 結合使用時英染,operation queue 可以為非并發(fā)的 operation 提供線程揽惹,因此,大部分的 operation 仍然可以異步執(zhí)行四康。

如果你想要手動地執(zhí)行一個 operation 搪搏,又想這個 operation 能夠異步執(zhí)行的話,你需要做一些額外的配置來讓你的 operation 支持并發(fā)執(zhí)行闪金。

下面列舉了一些你可能需要重寫的方法:

  • start :必須的疯溺,所有并發(fā)執(zhí)行的 operation 都必須要重寫這個方法,替換掉 NSOperation 類中的默認實現(xiàn)。start 方法是一個 operation 的起點囱嫩,我們可以在這里配置任務執(zhí)行的線程或者一些其它的執(zhí)行環(huán)境恃疯。另外,需要特別注意的是挠说,在我們重寫的 start 方法中一定不要調用父類的實現(xiàn)澡谭;

  • main :可選的,通常這個方法就是專門用來實現(xiàn)與該 operation 相關聯(lián)的任務的损俭。盡管我們可以直接在 start 方法中執(zhí)行我們的任務蛙奖,但是用 main 方法來實現(xiàn)我們的任務可以使設置代碼和任務代碼得到分離,從而使 operation 的結構更清晰杆兵;

  • isExecuting 和 isFinished :必須的雁仲,并發(fā)執(zhí)行的 operation 需要負責配置它們的執(zhí)行環(huán)境,并且向外界客戶報告執(zhí)行環(huán)境的狀態(tài)琐脏。因此攒砖,一個并發(fā)執(zhí)行的 operation 必須要維護一些狀態(tài)信息,用來記錄它的任務是否正在執(zhí)行日裙,是否已經完成執(zhí)行等吹艇。此外,當這兩個方法所代表的值發(fā)生變化時昂拂,我們需要生成相應的 KVO 通知受神,以便外界能夠觀察到這些狀態(tài)的變化;

  • isConcurrent :必須的格侯,這個方法的返回值用來標識一個 operation 是否是并發(fā)的 operation 鼻听,我們需要重寫這個方法并返回 YES 。

注意:當一個 operation 開始執(zhí)行后联四,它會一直執(zhí)行它的任務直到完成或被取消為止撑碴。我們可以在任意時間點取消一個 operation ,甚至是在它還未開始執(zhí)行之前朝墩。為了讓我們自定義的 operation 能夠支持取消事件醉拓,我們需要在代碼中定期地檢查 isCancelled 方法的返回值,一旦檢查到這個方法返回 YES 鱼辙,我們就需要立即停止執(zhí)行接下來的任務廉嚼。根據蘋果官方的說法,isCancelled 方法本身是足夠輕量的倒戏,所以就算是頻繁地調用它也不會給系統(tǒng)帶來太大的負擔怠噪。

簡單示例:

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

NS_ASSUME_NONNULL_BEGIN

@interface MyOperation : NSOperation



@end

NS_ASSUME_NONNULL_END

//MyOperation.m
#import "MyOperation.h"

@interface MyOperation()

@property (nonatomic, assign) BOOL executing;
@property (nonatomic, assign) BOOL finished;

@end

@implementation MyOperation

@synthesize executing = _executing;
@synthesize finished = _finished;

#pragma mark - start
- (void)start
{
    NSLog(@"任務開始");
    //開始執(zhí)行
    self.executing = YES;
    
    //執(zhí)行循環(huán)操作
    for (NSInteger i = 0; i < 10; ++i) {
        
        if ( self.cancelled ) {
            //如果任務已經被取消了,更改狀態(tài)后杜跷,返回
            self.executing = NO;
            self.finished = YES;
            NSLog(@"任務取消傍念,退出");
            return;
        }
        
        NSLog(@"Task %ld, thread:%@,executing=%d,cancelled=%d,finished=%d,operationCount=%lu", (long)i, [NSThread currentThread], self.executing, self.cancelled, self.finished, (unsigned long)[[NSOperationQueue currentQueue] operationCount]);
        [NSThread sleepForTimeInterval:0.2];
    }
    
    NSLog(@"任務結束");
    self.executing = NO;
    self.finished = YES;
    
    NSLog(@"end...thread:%@,executing=%d,cancelled=%d,finished=%d,operationCount=%lu", [NSThread currentThread], self.executing, self.cancelled, self.finished, (unsigned long)[[NSOperationQueue currentQueue] operationCount]);
    
}

//任務是否是并發(fā)
- (BOOL)isAsynchronous
{
    return YES;
}

#pragma mark - getter & setter

- (void)setExecuting:(BOOL)executing
{
    [self willChangeValueForKey:@"executing"];
    _executing = executing;
    [self didChangeValueForKey:@"executing"];
}

- (BOOL)executing
{
    return _executing;
}

- (void)setFinished:(BOOL)finished
{
    [self willChangeValueForKey:@"finished"];
    _finished = finished;
    [self didChangeValueForKey:@"finished"];
}

@end

使用MyOperation類:

- (void)customOperationAction
{
    MyOperation *operation = [[MyOperation alloc] init];
    NSOperationQueue *queue = [NSOperationQueue new];
    [queue addOperation:operation];
}

打印結果:

2019-07-22 15:28:09.950993+0800 NSOperationDemo[5038:140737] 任務開始
2019-07-22 15:28:09.951745+0800 NSOperationDemo[5038:140737] Task 0, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.153603+0800 NSOperationDemo[5038:140737] Task 1, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.354902+0800 NSOperationDemo[5038:140737] Task 2, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.557117+0800 NSOperationDemo[5038:140737] Task 3, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.758551+0800 NSOperationDemo[5038:140737] Task 4, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.960494+0800 NSOperationDemo[5038:140737] Task 5, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.163521+0800 NSOperationDemo[5038:140737] Task 6, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.363928+0800 NSOperationDemo[5038:140737] Task 7, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.566943+0800 NSOperationDemo[5038:140737] Task 8, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.767499+0800 NSOperationDemo[5038:140737] Task 9, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.969353+0800 NSOperationDemo[5038:140737] 任務結束
2019-07-22 15:28:11.969781+0800 NSOperationDemo[5038:140737] end...thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=0,cancelled=0,finished=1,operationCount=1

即使一個 operation 是被 cancel 掉了矫夷,我們仍然需要手動觸發(fā) isFinished 的 KVO 通知。因為當一個 operation 依賴其他 operation 時憋槐,它會觀察所有其他 operation 的 isFinished 的值的變化双藕,只有當它依賴的所有 operation 的 isFinished 的值為 YES 時,這個 operation 才能夠開始執(zhí)行阳仔。因此忧陪,如果一個我們自定義的 operation 被取消了但卻沒有手動觸發(fā) isFinished 的 KVO 通知的話,那么所有依賴它的 operation 都不會執(zhí)行近范。

4.4 NSOperationQueue 控制串行執(zhí)行嘶摊、并發(fā)執(zhí)行

操作隊列中有個關鍵屬性 maxConcurrentOperationCount,叫做最大并發(fā)操作數评矩。用來控制一個特定隊列中可以有多少個操作同時參與并發(fā)執(zhí)行叶堆。
maxConcurrentOperationCount的值為:

  • -1,默認情況就是-1斥杜,表示不進行限制虱颗,可進行并發(fā)執(zhí)行;
  • 0蔗喂,不會有任何操作執(zhí)行忘渔;
  • 1,隊列為串行隊列缰儿,只能串行執(zhí)行辨萍;
  • 大于1,隊列為并發(fā)隊列返弹,這個值也不是無限大,系統(tǒng)也會自動調整為 min{自己設定的值爪飘,系統(tǒng)設定的默認最大值}义起;

可以通過

@property (class, readonly, strong) NSOperationQueue *mainQueue API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

獲取到主隊列。

簡單示例:

- (void)testMacConcurrentOperationCount
{
    //創(chuàng)建 operation1
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任務1:---%@", [NSThread currentThread]);
        }
    }];
    
    //創(chuàng)建 operation2
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任務2:---%@", [NSThread currentThread]);
        }
    }];
    
    //創(chuàng)建 operation3
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任務3:---%@", [NSThread currentThread]);
        }
    }];
    
    //創(chuàng)建 operation4
    NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任務4:---%@", [NSThread currentThread]);
        }
    }];
    
    NSOperationQueue *queue = [NSOperationQueue new];
//    queue.maxConcurrentOperationCount = 0;
//    queue.maxConcurrentOperationCount = 1;
//    queue.maxConcurrentOperationCount = 2;
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    [queue addOperation:operation4];
}

最大并發(fā)操作數為0 打印結果:

沒有任何輸出师崎。

最大并發(fā)操作數為1 打印結果:

2019-07-22 15:44:09.151837+0800 NSOperationDemo[5356:150014] 任務1:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:10.155735+0800 NSOperationDemo[5356:150014] 任務1:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:11.161370+0800 NSOperationDemo[5356:150012] 任務2:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}
2019-07-22 15:44:12.167015+0800 NSOperationDemo[5356:150012] 任務2:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}
2019-07-22 15:44:13.172285+0800 NSOperationDemo[5356:150014] 任務3:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:14.172991+0800 NSOperationDemo[5356:150014] 任務3:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:15.175897+0800 NSOperationDemo[5356:150012] 任務4:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}
2019-07-22 15:44:16.181339+0800 NSOperationDemo[5356:150012] 任務4:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}

最大并發(fā)操作數為2 打印結果:

2019-07-22 15:45:02.487935+0800 NSOperationDemo[5377:150560] 任務1:---<NSThread: 0x60000040f0c0>{number = 4, name = (null)}
2019-07-22 15:45:02.487965+0800 NSOperationDemo[5377:150558] 任務2:---<NSThread: 0x600000428740>{number = 3, name = (null)}
2019-07-22 15:45:03.488880+0800 NSOperationDemo[5377:150558] 任務2:---<NSThread: 0x600000428740>{number = 3, name = (null)}
2019-07-22 15:45:03.488888+0800 NSOperationDemo[5377:150560] 任務1:---<NSThread: 0x60000040f0c0>{number = 4, name = (null)}
2019-07-22 15:45:04.494115+0800 NSOperationDemo[5377:150561] 任務4:---<NSThread: 0x60000040e940>{number = 5, name = (null)}
2019-07-22 15:45:04.494168+0800 NSOperationDemo[5377:150559] 任務3:---<NSThread: 0x60000040eb80>{number = 6, name = (null)}
2019-07-22 15:45:05.498621+0800 NSOperationDemo[5377:150561] 任務4:---<NSThread: 0x60000040e940>{number = 5, name = (null)}
2019-07-22 15:45:05.498621+0800 NSOperationDemo[5377:150559] 任務3:---<NSThread: 0x60000040eb80>{number = 6, name = (null)}

當最大并發(fā)操作數為0時默终,沒有任何操作執(zhí)行;
當最大并發(fā)操作數為1時犁罩,操作是按順序串行執(zhí)行的齐蔽;
當最大操作并發(fā)數為2時,操作是并發(fā)執(zhí)行的床估,可以同時執(zhí)行兩個操作含滴;

5. 操作之間的依賴

通過操作依賴,我們可以很方便的控制操作之間的執(zhí)行先后順序丐巫。
配置 operation 的依賴關系主要涉及到 NSOperation 類中的以下兩個方法:

//添加依賴谈况,使當前操作依賴于操作 op 的完成
- (void)addDependency:(NSOperation *)op;
//移除依賴勺美,取消當前操作對操作 op 的依賴
- (void)removeDependency:(NSOperation *)op;

簡單示例:

- (void)testDependency
{
    //創(chuàng)建 operation1
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任務1:---%@", [NSThread currentThread]);
        }
    }];
    
    //創(chuàng)建 operation2
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任務2:---%@", [NSThread currentThread]);
        }
    }];
    
    [operation1 addDependency:operation2];
    
    NSOperationQueue *queue = [NSOperationQueue new];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
}

打印結果:

2019-07-22 16:29:06.178541+0800 NSOperationDemo[5934:164383] 任務2:---<NSThread: 0x600000d8dbc0>{number = 3, name = (null)}
2019-07-22 16:29:07.184051+0800 NSOperationDemo[5934:164383] 任務2:---<NSThread: 0x600000d8dbc0>{number = 3, name = (null)}
2019-07-22 16:29:08.189099+0800 NSOperationDemo[5934:164386] 任務1:---<NSThread: 0x600000de8380>{number = 4, name = (null)}
2019-07-22 16:29:09.194669+0800 NSOperationDemo[5934:164386] 任務1:---<NSThread: 0x600000de8380>{number = 4, name = (null)}

注意:
- 不要造成循環(huán)依賴,不然會出現(xiàn)等待的情況碑韵,導致操作都不發(fā)執(zhí)行赡茸;
- 另外添加依賴要在隊列添加操作之前,因為隊列添加操作后祝闻,什么時候執(zhí)行是系統(tǒng)決定的占卧,有可能執(zhí)行會比添加依賴還更快;

6. NSOperation 隊列中的優(yōu)先級

對于被添加到 操作隊列 中的 操作 來說联喘,決定它們執(zhí)行順序的第一要素是它們的 isReady 狀態(tài)华蜒,其次是它們在隊列中的優(yōu)先級。操作 的 isReady 狀態(tài)取決于它的依賴關系耸袜,而在隊列中的優(yōu)先級則是 操作本身的屬性友多。默認情況下,所有新創(chuàng)建的操作在隊列中的優(yōu)先級都是 NSOperationQueuePriorityNormal 的堤框,但是我們可以根據需要通過改變queuePriority屬性值

@property NSOperationQueuePriority queuePriority;

來提高或降低 operation 的隊列優(yōu)先級域滥。

優(yōu)先級的取值有:

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

需要注意的是,隊列優(yōu)先級只應用于相同操作隊列中的操作之間蜈抓,不同操作隊列中的操作不受此影響启绰。

另外,隊列優(yōu)先級和依賴關系之間的關系:

  • 操作的隊列優(yōu)先級只決定當前所有 isReady 狀態(tài)為 YES 的操作的執(zhí)行順序沟使。比如委可,在一個操作隊列中,有一個高優(yōu)先級和一個低優(yōu)先級的操作 腊嗡,并且它們的 isReady 狀態(tài)都為 YES 着倾,那么高優(yōu)先級的 operation 將會優(yōu)先執(zhí)行。而如果這個高優(yōu)先級的操作的 isReady 狀態(tài)為 NO 燕少,而低優(yōu)先級的操作的 isReady 狀態(tài)為 YES 的話卡者,那么這個低優(yōu)先級的操作反而會優(yōu)先執(zhí)行妥曲;
  • 操作的隊列中有一個準備就緒狀態(tài)的操作和一個未準備就緒的操作脐湾,未準備就緒的操作優(yōu)先級比準備就緒的操作優(yōu)先級高。但是準備就緒的操作依賴于未準備就緒操作的完成颤枪。雖然準備就緒的操作優(yōu)先級低底挫,也會優(yōu)先執(zhí)行恒傻。優(yōu)先級不能取代依賴關系。如果要控制操作間的執(zhí)行順序建邓,則必須使用依賴關系盈厘;

7. 線程之間的通信

場景:耗時操作,比如下載操作會放在子線程中處理涝缝,當子線程結束后扑庞,需要回到主線程中設置刷新UI譬重。

簡單示例:

- (void)threadCommunication
{
    //1 創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //2 創(chuàng)建操作
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        
        //2.1 執(zhí)行耗時操作
        NSURL *url = [NSURL URLWithString:@"https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1902385933,516700697&fm=26&gp=0.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        NSLog(@"圖片下載完畢-----%@",[NSThread currentThread]);
        
        //2.2 獲取主隊列,回到主線程執(zhí)行block里面的任務
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            //在主線程中罐氨,刷新UI
            self.imageView.image = image;
        }];
    }];
    
    //3 加入隊列
    [queue addOperation:operation];
}

8. 暫停和恢復操作隊列

如果我們想要暫停和恢復執(zhí)行操作隊列中的操作 臀规,可以通過調用 操作隊列的 setSuspended: 方法來實現(xiàn)這個目的。不過需要注意的是栅隐,暫停執(zhí)行操作隊列并不能使正在執(zhí)行的操作暫停執(zhí)行塔嬉,而只是簡單地暫停調度新的操作。另外租悄,我們并不能單獨地暫停執(zhí)行一個操作谨究,除非直接 cancel 掉。

暫停和取消的區(qū)別就在于:暫停操作之后還可以恢復操作泣棋,繼續(xù)向下執(zhí)行胶哲;而取消操作之后,所有的操作就清空了潭辈,無法再接著執(zhí)行剩下的操作鸯屿。

參考資料:
iOS 并發(fā)編程之 Operation Queues

Demo傳送門

over!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市把敢,隨后出現(xiàn)的幾起案子寄摆,更是在濱河造成了極大的恐慌,老刑警劉巖修赞,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婶恼,死亡現(xiàn)場離奇詭異,居然都是意外死亡柏副,警方通過查閱死者的電腦和手機勾邦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來割择,“玉大人检痰,你說我怎么就攤上這事∠峭疲” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵公壤,是天一觀的道長换可。 經常有香客問我,道長厦幅,這世上最難降的妖魔是什么沾鳄? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮确憨,結果婚禮上译荞,老公的妹妹穿的比我還像新娘瓤的。我一直安慰自己,他們只是感情好吞歼,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布圈膏。 她就那樣靜靜地躺著,像睡著了一般篙骡。 火紅的嫁衣襯著肌膚如雪稽坤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天糯俗,我揣著相機與錄音尿褪,去河邊找鬼。 笑死得湘,一個胖子當著我的面吹牛杖玲,可吹牛的內容都是我干的。 我是一名探鬼主播淘正,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼摆马,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了跪帝?” 一聲冷哼從身側響起今膊,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎伞剑,沒想到半個月后斑唬,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡黎泣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年恕刘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抒倚。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡褐着,死狀恐怖,靈堂內的尸體忽然破棺而出托呕,到底是詐尸還是另有隱情含蓉,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布项郊,位于F島的核電站馅扣,受9級特大地震影響,放射性物質發(fā)生泄漏着降。R本人自食惡果不足惜差油,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望任洞。 院中可真熱鬧蓄喇,春花似錦发侵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至楼眷,卻和暖如春铲汪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背罐柳。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工掌腰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人张吉。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓齿梁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肮蛹。 傳聞我的和親對象是個殘疾皇子勺择,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內容