iOS開發(fā)之多線程編程總結(jié)(三)

前言

前段時(shí)間的心病落下帷幕后孕锄,一大波需求向我迎來迹冤,忙的我最近沒時(shí)間更新博客了入挣,只能在閑暇的時(shí)間吹吹牛逼了嚣州。這篇博客主要講解NSOperation的一些知識(shí)鲫售。

busy Time.jpg

1. NSOperation簡(jiǎn)介

NSOperation是蘋果提供給我們的一套多線程解決方案。實(shí)際上NSOperation是基于GCD更高一層的封裝该肴,但是比GCD更簡(jiǎn)單易用情竹、代碼可讀性也更高。

  • GCD :則是一種更輕量級(jí)的匀哄,是基于C語言實(shí)現(xiàn)的秦效,以 FIFO 的順序執(zhí)行并發(fā)任務(wù)的方式,使用 GCD 時(shí)我們并不關(guān)心任務(wù)的調(diào)度情況拱雏,而讓系統(tǒng)幫我們自動(dòng)處理棉安。但是 GCD 的短板也是非常明顯的,比如我們想要給任務(wù)之間添加依賴關(guān)系铸抑、取消或者暫停一個(gè)正在執(zhí)行的任務(wù)時(shí)就會(huì)變得非常棘手贡耽。

  • Operation Queues :相對(duì) GCD 來說,使用 Operation Queues 會(huì)增加一點(diǎn)點(diǎn)額外的開銷,但是我們卻換來了非常強(qiáng)大的靈活性和功能蒲赂,我們可以給 operation 之間添加依賴關(guān)系阱冶、取消一個(gè)正在執(zhí)行的 operation 、暫停和恢復(fù) operation queue 等滥嘴。

因?yàn)镹SOperation是基于GCD的木蹬,那么使用起來也和GCD差不多,其中若皱,NSOperation相當(dāng)于GCD中的任務(wù)镊叁,而NSOperationQueue則相當(dāng)于GCD中的隊(duì)列。NSOperation實(shí)現(xiàn)多線程的使用步驟分為三步:

  • 1.創(chuàng)建任務(wù):先將需要執(zhí)行的操作封裝到一個(gè)NSOperation對(duì)象中走触。

  • 2.創(chuàng)建隊(duì)列:創(chuàng)建NSOperationQueue對(duì)象晦譬。

  • 3.將任務(wù)加入到隊(duì)列中:然后將NSOperation對(duì)象添加到NSOperationQueue中。

之后互广,系統(tǒng)就會(huì)自動(dòng)將NSOperationQueue中的NSOperation取出來敛腌,在新線程中執(zhí)行操作。

下面就跟我一起學(xué)習(xí)NSOperation的相關(guān)知識(shí)點(diǎn)惫皱。

2. NSOperation和NSOperationQueue的基本使用

1. 創(chuàng)建任務(wù)

在默認(rèn)情況下像樊,NSOperation 是同步執(zhí)行的,也就是說會(huì)阻塞當(dāng)前線程直到任務(wù)完成旅敷。

NSOperation 本身是一個(gè)抽象類生棍,不能直接實(shí)例化,因此媳谁,如果我們想要使用它來執(zhí)行具體任務(wù)的話足绅,就必須使用系統(tǒng)預(yù)定義的兩個(gè)子類NSInvocationOperationNSBlockOperation或者創(chuàng)建自己的子類

  • 1.使用子類NSInvocationOperation
  • 2.使用子類NSBlockOperation
  • 3.定義繼承自NSOperation的子類韩脑,通過實(shí)現(xiàn)內(nèi)部相應(yīng)的方法來創(chuàng)建任務(wù)。

1.使用子類NSInvocationOperation

NSInvocationOperation:我們可以通過一個(gè) object 和 selector 非常方便地創(chuàng)建一個(gè) NSInvocationOperation 粹污,這是一種非常動(dòng)態(tài)和靈活的方式段多。

NSInvocationOperation方法:

- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;

NSInvocationOperation-Demo:

- (void)invocationOperation{
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    op.completionBlock = ^{
        NSLog(@"任務(wù)完成后回調(diào)block");
    };
    
    [op start];
}
- (void)run{
    NSLog(@"------%@", [NSThread currentThread]);
}

2.使用子類NSBlockOperation

** NSBlockOperation:**我們可以使用 NSBlockOperation 來并發(fā)執(zhí)行一個(gè)或多個(gè) block ,只有當(dāng)一個(gè) NSBlockOperation 所關(guān)聯(lián)的所有 block 都執(zhí)行完畢時(shí)(會(huì)阻塞當(dāng)前線程)壮吩,這個(gè) NSBlockOperation 才算執(zhí)行完成进苍,有點(diǎn)類似于 dispatch_group 的概念。

** NSBlockOperation方法:**

+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;

** NSBlockOperation-Demo:**

- (void)blockOperationAddTask{
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"------%@", [NSThread currentThread]);
    }];
    for (int i = 0; i < 5; i++) {
        [op addExecutionBlock:^{
            NSLog(@"%d------%@", i,[NSThread currentThread]);
        }];
    }
    
    [op start];
}

打印結(jié)果:

2016-11-07 21:12:34.531 ThreadDemo[1667:25954] ------<NSThread: 0x600000061d80>{number = 1, name = main}
2016-11-07 21:12:34.531 ThreadDemo[1667:25954] 3------<NSThread: 0x600000061d80>{number = 1, name = main}
2016-11-07 21:12:34.532 ThreadDemo[1667:25954] 4------<NSThread: 0x600000061d80>{number = 1, name = main}
2016-11-07 21:12:34.531 ThreadDemo[1667:28309] 2------<NSThread: 0x6000000747c0>{number = 8, name = (null)}
2016-11-07 21:12:34.531 ThreadDemo[1667:28308] 1------<NSThread: 0x600000077280>{number = 7, name = (null)}
2016-11-07 21:12:34.531 ThreadDemo[1667:28307] 0------<NSThread: 0x608000079500>{number = 6, name = (null)}

** NSBlockOperation注意點(diǎn):**

  • 從上面打印結(jié)果看到在多個(gè)線程執(zhí)行任務(wù)鸭叙。addExecutionBlock:可以為NSBlockOperation添加額外的操作觉啊。如果當(dāng)前NSOperation的任務(wù)只有一個(gè)的話,那肯定不會(huì)開辟一個(gè)新的線程沈贝,只能同步執(zhí)行杠人。只有NSOperation的任務(wù)數(shù)>1的時(shí)候,這些額外的操作才有可能在其他線程并發(fā)執(zhí)行。注意我的用詞 "才有可能"嗡善,也就是說額外的操作也有可能在當(dāng)前線程里執(zhí)行辑莫。

3. 定義繼承自NSOperation的子類

NSOperation的子類:當(dāng)系統(tǒng)預(yù)定義的兩個(gè)子類 NSInvocationOperationNSBlockOperation 不能很好的滿足我們的需求時(shí),我們可以自定義自己的 NSOperation 子類罩引,添加我們想要的功能各吨。我們可以自定義非并發(fā)和并發(fā)兩種不同類型的 NSOperation 子類,而自定義一個(gè)前者要比后者簡(jiǎn)單得多袁铐。我們先來一個(gè)簡(jiǎn)單的非并發(fā)的NSOperation 子類揭蜒,并發(fā)的NSOperation的單獨(dú)在后面講解!

非并發(fā)的NSOperation 子類Demo:

先定義一個(gè)繼承自NSOperation的子類剔桨,重寫main方法
JYSerialOperation.h

#import <Foundation/Foundation.h>

typedef void (^JYCompletionBlock)(NSData *imageData);

@interface JYSerialOperation : NSOperation

@property (nonatomic, copy) JYCompletionBlock comBlock;

@end

JYSerialOperation.m

#import "JYSerialOperation.h"

@implementation JYSerialOperation

- (void)main{
    @autoreleasepool {
        if (self.isCancelled) {
            return;
        }
        NSURL *url=[NSURL URLWithString:@"https://p1.bpimg.com/524586/475bc82ff016054ds.jpg"];
        NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
        
        if (!imageData) {
            imageData = nil;
        }
        
        if (self.isCancelled) return;
        
        [self performSelectorOnMainThread:@selector(completionAction:) withObject:imageData waitUntilDone:NO];
    
    }
}

- (void)completionAction:(NSData *)imageData{
    if (self.comBlock) {
        self.comBlock(imageData);
    }
}

@end

使用的時(shí)候?qū)腩^文件然后調(diào)用:

//在主線程中執(zhí)行并沒有開辟線程
JYSerialOperation *op = [[JYSerialOperation alloc] init];
[op start];

op.comBlock = ^(NSData *imageData){
    UIImage *image = [UIImage imageWithData:imageData];
    self.imageView.image = image;
};

2. 創(chuàng)建隊(duì)列

和GCD中的并發(fā)隊(duì)列屉更、串行隊(duì)列略有不同的是:NSOperationQueue一共有兩種隊(duì)列:主隊(duì)列、其他隊(duì)列领炫。其中其他隊(duì)列同時(shí)包含了串行偶垮、并發(fā)功能。下邊是主隊(duì)列帝洪、其他隊(duì)列的基本創(chuàng)建方法和特點(diǎn)似舵。

主隊(duì)列

  • 凡是添加到主隊(duì)列中的任務(wù)(NSOperation),都會(huì)放到主線程中執(zhí)行
NSOperationQueue *queue = [NSOperationQueue mainQueue];

其他隊(duì)列(非主隊(duì)列)

  • 添加到這種隊(duì)列中的任務(wù)(NSOperation)葱峡,就會(huì)自動(dòng)放到子線程中執(zhí)行
  • 同時(shí)包含了:串行砚哗、并發(fā)功能
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    

3. 把任務(wù)加入到隊(duì)列中

只要將任務(wù)加入到隊(duì)列中,就不要執(zhí)行start方法,隊(duì)列會(huì)負(fù)責(zé)調(diào)度任務(wù)自動(dòng)執(zhí)行start方法砰奕。加入隊(duì)列的方法如下:

- (void)addOperation:(NSOperation *)op;//添加單個(gè)任務(wù)
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);//添加任務(wù)數(shù)組

- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);//添加單個(gè)任務(wù)

1.- (void)addOperation:(NSOperation *)op;

  • 首先創(chuàng)建任務(wù)operation蛛芥,然后將創(chuàng)建好的任務(wù)添加隊(duì)列!
- (void)addOperationToQueue{
    
    // 1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 2. 創(chuàng)建操作
    // 創(chuàng)建NSInvocationOperation
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2) object:nil];
    // 創(chuàng)建NSBlockOperation
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"op2---->%d-----%@", i,[NSThread currentThread]);
        }
    }];
    
    // 3. 添加操作到隊(duì)列中:addOperation:
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
}

- (void)run2{
    for (int i = 0; i < 5; i++) {
        NSLog(@"op1---->%d-----%@",i, [NSThread currentThread]);
    }
}
  • 打印結(jié)果:
2016-11-07 22:03:44.125 ThreadDemo[2761:55463] op1---->0-----<NSThread: 0x60000007f980>{number = 3, name = (null)}
2016-11-07 22:03:44.125 ThreadDemo[2761:55484] op2---->0-----<NSThread: 0x60000007fd80>{number = 4, name = (null)}
2016-11-07 22:03:44.126 ThreadDemo[2761:55484] op2---->1-----<NSThread: 0x60000007fd80>{number = 4, name = (null)}
2016-11-07 22:03:44.126 ThreadDemo[2761:55463] op1---->1-----<NSThread: 0x60000007f980>{number = 3, name = (null)}
2016-11-07 22:03:44.127 ThreadDemo[2761:55484] op2---->2-----<NSThread: 0x60000007fd80>{number = 4, name = (null)}
2016-11-07 22:03:44.127 ThreadDemo[2761:55463] op1---->2-----<NSThread: 0x60000007f980>{number = 3, name = (null)}
2016-11-07 22:03:44.127 ThreadDemo[2761:55484] op2---->3-----<NSThread: 0x60000007fd80>{number = 4, name = (null)}
2016-11-07 22:03:44.128 ThreadDemo[2761:55463] op1---->3-----<NSThread: 0x60000007f980>{number = 3, name = (null)}
2016-11-07 22:03:44.128 ThreadDemo[2761:55463] op1---->4-----<NSThread: 0x60000007f980>{number = 3, name = (null)}
2016-11-07 22:03:44.128 ThreadDemo[2761:55484] op2---->4-----<NSThread: 0x60000007fd80>{number = 4, name = (null)}
  • 從上面可以看到NSOperation Queue會(huì)開辟線程军援。然后并發(fā)執(zhí)行仅淑!

2.- (void)addOperationWithBlock:(void (^)(void))block ;

  • 無需先創(chuàng)建任務(wù),在block中添加任務(wù)胸哥,直接將任務(wù)block加入到隊(duì)列中涯竟。

- (void)addOperationWithBlockToQueue{
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 設(shè)置最大并發(fā)操作數(shù)
    //    queue.maxConcurrentOperationCount = 1;// 就變成了串行隊(duì)列
    queue.maxConcurrentOperationCount = 5;

    for (int i = 0; i < 5; i++) {
        [queue addOperationWithBlock:^{
            NSLog(@"%d-----%@",i, [NSThread currentThread]);
        }];
    }
    
}
  • 打印結(jié)果:
2016-11-07 22:13:14.189 ThreadDemo[2933:60785] 2-----<NSThread: 0x600000274840>{number = 10, name = (null)}
2016-11-07 22:13:14.189 ThreadDemo[2933:60814] 0-----<NSThread: 0x608000260f00>{number = 14, name = (null)}
2016-11-07 22:13:14.189 ThreadDemo[2933:60803] 1-----<NSThread: 0x600000275600>{number = 11, name = (null)}
2016-11-07 22:13:14.189 ThreadDemo[2933:60812] 4-----<NSThread: 0x600000274a40>{number = 13, name = (null)}
2016-11-07 22:13:14.189 ThreadDemo[2933:60695] 3-----<NSThread: 0x608000260a00>{number = 9, name = (null)}

可以看出addOperationWithBlock:和NSOperationQueue能夠開啟新線程,進(jìn)行并發(fā)執(zhí)行空厌。

3. 隊(duì)列的重要屬性maxConcurrentOperationCount

maxConcurrentOperationCount隊(duì)列的最大并發(fā)數(shù)庐船,也就是當(dāng)前執(zhí)行隊(duì)列的任務(wù)時(shí),最多開辟多少條線程嘲更!具體開多少條線程是由底層線程池來決定筐钟。

隊(duì)列是串行還是并發(fā)就是由maxConcurrentOperationCount來決定

  • maxConcurrentOperationCount默認(rèn)情況下為-1,表示不進(jìn)行限制赋朦,默認(rèn)為并發(fā)執(zhí)行篓冲。
  • 當(dāng)maxConcurrentOperationCount為1時(shí)李破,進(jìn)行串行執(zhí)行。
  • 當(dāng)maxConcurrentOperationCount大于1時(shí)纹因,進(jìn)行并發(fā)執(zhí)行喷屋,當(dāng)然這個(gè)值不應(yīng)超過系統(tǒng)限制,即使自己設(shè)置一個(gè)很大的值瞭恰,系統(tǒng)也會(huì)自動(dòng)調(diào)整屯曹。

代碼參考上一個(gè)- (void)addOperationWithBlock:(void (^)(void))block ;Demo,修改最大并發(fā)數(shù)即可測(cè)試,結(jié)果如下:

//maxConcurrentOperationCount=1
2016-11-08 10:05:34.748 ThreadDemo[1224:14753] 0-----<NSThread: 0x600000263100>{number = 4, name = (null)}
2016-11-08 10:05:34.749 ThreadDemo[1224:14753] 1-----<NSThread: 0x600000263100>{number = 4, name = (null)}
2016-11-08 10:05:34.749 ThreadDemo[1224:14753] 2-----<NSThread: 0x600000263100>{number = 4, name = (null)}
2016-11-08 10:05:34.750 ThreadDemo[1224:14754] 3-----<NSThread: 0x6000002635c0>{number = 3, name = (null)}
2016-11-08 10:05:34.750 ThreadDemo[1224:14754] 4-----<NSThread: 0x6000002635c0>{number = 3, name = (null)}

注意點(diǎn):

  • maxConcurrentOperationCount設(shè)置為1時(shí)惊畏,是串行隊(duì)列恶耽,也有可能開辟多條線程。串行只是一種執(zhí)行任務(wù)的方式颜启,跟開辟線程是不同緯度的概念別弄混了偷俭,同步和異步?jīng)Q定開不開線程,可以參考上一篇博客iOS開發(fā)之多線程編程總結(jié)(二)的基本概念缰盏。

  • maxConcurrentOperationCount設(shè)置為1時(shí)涌萤,是串行隊(duì)列,但是 operation 的執(zhí)行順序還是一樣會(huì)受其他因素影響的口猜,比如 operation 的 isReady 狀態(tài)负溪、operation 的隊(duì)列優(yōu)先級(jí)等,如果operation 的執(zhí)行順序?qū)ξ覀儊碚f非常重要济炎,那么我們就應(yīng)該在將 operation 添加到 operation queue 之前就建立好它的依賴關(guān)系川抡。

4. 任務(wù)的操作依賴

通過配置依賴關(guān)系,我們可以讓不同的 operation 串行執(zhí)行须尚,正如我們上面剛剛提到的最大并發(fā)數(shù)為1時(shí)串行執(zhí)行(但是順序不一定會(huì)是我們想要的順序)崖堤,一個(gè) operation 只有在它依賴的所有 operation 都執(zhí)行完成后才能開始執(zhí)行。配置 operation 的依賴關(guān)系主要涉及到NSOperation 類中的以下兩個(gè)方法:

- (void)addDependency:(NSOperation *)op;//添加依賴
- (void)removeDependency:(NSOperation *)op;//刪除依賴
  • 特別注意1:addDependency:方法添加的依賴關(guān)系是單向的耐床,比如 [A addDependency:B];密幔,表示 A 依賴 B,B 并不依賴 A 撩轰。

  • 特別注意2:addDependency:可以跨隊(duì)列添加依賴老玛,原因:上面添加依賴關(guān)系的方法是存在于 NSOperation 類中的,operation 的依賴關(guān)系是它自己管理的钧敞,與它被添加到哪個(gè) operation queue 無關(guān)。

  • 特別注意3:千萬不要在 operation 之間添加循環(huán)依賴麸粮,這樣會(huì)導(dǎo)致這些 operation 都不會(huì)被執(zhí)行溉苛。

任務(wù)的操作依賴Demo:

#pragma mark --------------操作依賴
- (void)operateDependency{

    NSMutableArray *array = [NSMutableArray array];
    
    //創(chuàng)建任務(wù)
    for (int i = 0; i < 10; i++) {
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"________第%d個(gè)任務(wù)%@____",i,[NSThread currentThread]);
        }];
        op.name = [NSString stringWithFormat:@"op%d",i];
        
        [array addObject:op];
    }
    
    //創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    queue.name = @"queue";
    
    //設(shè)置依賴 可以跨隊(duì)列依賴。
    for (int i = 0; i < array.count - 1; i++) {
        //依次依賴,下面相當(dāng)于同步執(zhí)行了
        NSBlockOperation *op1 = [array objectAtIndex:i];
        NSBlockOperation *op2 = [array objectAtIndex:i+1];
        [op2 addDependency:op1];
        
//        //修改 Operation 在隊(duì)列中的優(yōu)先級(jí)
//        if (i == 6) {
//            [op1 setQueuePriority:NSOperationQueuePriorityVeryHigh];
//        }
//
//        if (i > 4) {
//            //刪除依賴
//            [op2 removeDependency:op1];
//        }
    }
    
//    //需求:第5個(gè)任務(wù)完成后取消隊(duì)列任務(wù)
//    NSBlockOperation *op1 = [array objectAtIndex:4];
//    op1.completionBlock = ^{
//        //取消隊(duì)列中未執(zhí)行的所有任務(wù)
//        [queue cancelAllOperations];
//    };
    
    //添加任務(wù)到隊(duì)列中
    [queue addOperations:array waitUntilFinished:NO];
    
}

打印結(jié)果:

2016-11-08 10:37:37.505 ThreadDemo[1224:29218] ________第0個(gè)任務(wù)<NSThread: 0x600000263340>{number = 6, name = (null)}____
2016-11-08 10:37:37.506 ThreadDemo[1224:29228] ________第1個(gè)任務(wù)<NSThread: 0x608000263080>{number = 7, name = (null)}____
2016-11-08 10:37:37.506 ThreadDemo[1224:29228] ________第2個(gè)任務(wù)<NSThread: 0x608000263080>{number = 7, name = (null)}____
2016-11-08 10:37:37.507 ThreadDemo[1224:29218] ________第3個(gè)任務(wù)<NSThread: 0x600000263340>{number = 6, name = (null)}____
2016-11-08 10:37:37.507 ThreadDemo[1224:29228] ________第4個(gè)任務(wù)<NSThread: 0x608000263080>{number = 7, name = (null)}____
2016-11-08 10:37:37.508 ThreadDemo[1224:29218] ________第5個(gè)任務(wù)<NSThread: 0x600000263340>{number = 6, name = (null)}____
2016-11-08 10:37:37.508 ThreadDemo[1224:29218] ________第6個(gè)任務(wù)<NSThread: 0x600000263340>{number = 6, name = (null)}____
2016-11-08 10:37:37.509 ThreadDemo[1224:29228] ________第7個(gè)任務(wù)<NSThread: 0x608000263080>{number = 7, name = (null)}____
2016-11-08 10:37:37.510 ThreadDemo[1224:29218] ________第8個(gè)任務(wù)<NSThread: 0x600000263340>{number = 6, name = (null)}____
2016-11-08 10:37:37.511 ThreadDemo[1224:29228] ________第9個(gè)任務(wù)<NSThread: 0x608000263080>{number = 7, name = (null)}____

5. 其他方法介紹:

NSOperation方法:

BOOL cancelled;//判斷任務(wù)是否取消
BOOL executing; //判斷任務(wù)是否正在執(zhí)行
BOOL finished; //判斷任務(wù)是否完成
BOOL concurrent;//判斷任務(wù)是否是并發(fā)
NSOperationQueuePriority queuePriority;//修改 Operation 執(zhí)行任務(wù)線程的優(yōu)先級(jí)
void (^completionBlock)(void) //用來設(shè)置完成后需要執(zhí)行的操作
- (void)cancel; //取消任務(wù)
- (void)waitUntilFinished; //阻塞當(dāng)前線程直到此任務(wù)執(zhí)行完畢

NSOperation Queue方法:

NSUInteger operationCount; //獲取隊(duì)列的任務(wù)數(shù)
- (void)cancelAllOperations; //取消隊(duì)列中所有的任務(wù)
- (void)waitUntilAllOperationsAreFinished; //阻塞當(dāng)前線程直到此隊(duì)列中的所有任務(wù)執(zhí)行完畢
[queue setSuspended:YES]; // 暫停queue
[queue setSuspended:NO]; // 繼續(xù)queue

補(bǔ)充知識(shí)點(diǎn):

  • 取消任務(wù):當(dāng)一個(gè) operation 開始執(zhí)行后弄诲,它會(huì)一直執(zhí)行它的任務(wù)直到完成或被取消為止愚战。我們可以在任意時(shí)間點(diǎn)取消一個(gè) operation 娇唯,甚至是在它還未開始執(zhí)行之前。為了讓我們自定義的 operation 能夠支持取消事件寂玲,我們需要在代碼中定期地檢查 isCancelled 方法的返回值塔插,一旦檢查到這個(gè)方法返回 YES ,我們就需要立即停止執(zhí)行接下來的任務(wù)拓哟。根據(jù)蘋果官方的說法想许,isCancelled 方法本身是足夠輕量的,所以就算是頻繁地調(diào)用它也不會(huì)給系統(tǒng)帶來太大的負(fù)擔(dān)断序。

    The isCancelled method itself is very lightweight and can be called frequently without any significant performance penalty.

    通常來說流纹,當(dāng)我們自定義一個(gè) operation 類時(shí),我們需要考慮在以下幾個(gè)關(guān)鍵點(diǎn)檢查 isCancelled 方法的返回值:

    • 在真正開始執(zhí)行任務(wù)之前违诗;
    • 至少在每次循環(huán)中檢查一次漱凝,而如果一次循環(huán)的時(shí)間本身就比較長(zhǎng)的話,則需要檢查得更加頻繁诸迟;
    • 在任何相對(duì)來說比較容易中止 operation 的地方茸炒。

    看到這里,我想你應(yīng)該可以意識(shí)到一點(diǎn)阵苇,那就是盡管 operation 是支持取消操作的壁公,但卻并不是立即取消的,而是在你調(diào)用了 operation 的 cancel 方法之后的下一個(gè) isCancelled 的檢查點(diǎn)取消的慎玖。

  • 任務(wù)在隊(duì)列中的優(yōu)先級(jí):對(duì)于被添加到 operation queue 中的 operation 來說贮尖,決定它們執(zhí)行順序的第一要素是它們的 isReady 狀態(tài),其次是它們?cè)陉?duì)列中的優(yōu)先級(jí)趁怔。operation 的 isReady 狀態(tài)取決于它的依賴關(guān)系湿硝,而在隊(duì)列中的優(yōu)先級(jí)則是 operation 本身的屬性。默認(rèn)情況下润努,所有新創(chuàng)建的 operation 的隊(duì)列優(yōu)先級(jí)都是 normal 的关斜,但是我們可以根據(jù)需要通過setQueuePriority: 方法來提高或降低 operation 的隊(duì)列優(yōu)先級(jí)。

    • 優(yōu)先級(jí)只是大概的判斷铺浇,跟GCD中的全局隊(duì)列功能相似痢畜,并不能依賴這個(gè)做嚴(yán)格的任務(wù)順序

    • 隊(duì)列優(yōu)先級(jí)只應(yīng)用于相同 operation queue 中的 operation 之間,不同 operation queue 中的 operation 不受此影響

  • completionBlock:一個(gè) operation 可以在它的主任務(wù)執(zhí)行完成時(shí)回調(diào)一個(gè) completion block 鳍侣。我們可以用 completion block 來執(zhí)行一些主任務(wù)之外的工作丁稀。

    • 當(dāng)一個(gè) operation 被取消時(shí),它的 completion block 仍然會(huì)執(zhí)行倚聚,所以我們需要在真正執(zhí)行代碼前檢查一下 isCancelled 方法的返回值线衫。

    • 我們也沒有辦法保證 completion block 被回調(diào)時(shí)一定是在主線程,理論上它應(yīng)該是與觸發(fā) isFinished 的 KVO 通知所在的線程一致的惑折,所以如果有必要的話我們可以在 completion block 中使用 GCD 來保證從主線程更新 UI 授账。

  • 暫停和恢復(fù) Operation Queue:暫停執(zhí)行 operation queue 并不能使正在執(zhí)行的 operation 暫停執(zhí)行枯跑,而只是簡(jiǎn)單地暫停調(diào)度新的 operation 。另外白热,我們并不能單獨(dú)地暫停執(zhí)行一個(gè) operation 敛助,除非直接 cancel 掉。

6. 并發(fā)的NSOperation

在上面創(chuàng)建自定義子類NSOperation任務(wù)的時(shí)候只是創(chuàng)建了串行的NSOperation子類屋确,只要重寫main方法即可∧苫鳎現(xiàn)在我們就來看看如何實(shí)現(xiàn)并發(fā)的子類NSOperation。

NSOperation有三個(gè)狀態(tài)量isCancelled, isExecutingisFinished.

實(shí)現(xiàn)并發(fā)(concurrent)的NSOperation步驟:

  1. 重寫start()函數(shù)
  • 必須的乍恐,所有并發(fā)執(zhí)行的 operation 都必須要重寫這個(gè)方法评疗,替換掉 NSOperation 類中的默認(rèn)實(shí)現(xiàn)。start 方法是一個(gè) operation 的起點(diǎn)茵烈,我們可以在這里配置任務(wù)執(zhí)行的線程或者一些其它的執(zhí)行環(huán)境百匆。另外,需要特別注意的是呜投,在我們重寫的 start 方法中一定不要調(diào)用父類的實(shí)現(xiàn)加匈。
  1. 重寫main函數(shù)
  • 可選的,通常這個(gè)方法就是專門用來實(shí)現(xiàn)與該 operation 相關(guān)聯(lián)的任務(wù)的仑荐。盡管我們可以直接在 start 方法中執(zhí)行我們的任務(wù)雕拼,但是用 main 方法來實(shí)現(xiàn)我們的任務(wù)可以使設(shè)置代碼和任務(wù)代碼得到分離,從而使 operation 的結(jié)構(gòu)更清晰粘招。
  1. isExecuting 和 isFinished
  • 必須的啥寇,并發(fā)執(zhí)行的 operation 需要負(fù)責(zé)配置它們的執(zhí)行環(huán)境,并且向外界客戶報(bào)告執(zhí)行環(huán)境的狀態(tài)洒扎。因此辑甜,一個(gè)并發(fā)執(zhí)行的 operation 必須要維護(hù)一些狀態(tài)信息,用來記錄它的任務(wù)是否正在執(zhí)行袍冷,是否已經(jīng)完成執(zhí)行等磷醋。此外,當(dāng)這兩個(gè)方法所代表的值發(fā)生變化時(shí)胡诗,我們需要生成相應(yīng)的 KVO 通知邓线,以便外界能夠觀察到這些狀態(tài)的變化。

  • 在并發(fā)情況下系統(tǒng)不知道operation什么時(shí)候finished, operation里面的task一般來說是異步執(zhí)行的, 也就是start函數(shù)返回了operation不一定就是finish了, 這個(gè)你自己來控制, 你什么時(shí)候?qū)sFinished置為YES(發(fā)送相應(yīng)的KVO消息), operation就什么時(shí)候完成了煌恢。

  1. 重寫isConcurrent函數(shù)
  • 必須的骇陈,這個(gè)方法的返回值用來標(biāo)識(shí)一個(gè) operation 是否是并發(fā)的 operation ,我們需要重寫這個(gè)方法并返回 YES 瑰抵。

并發(fā)的NSOperationDemo:

JYConcurrentOperation2.h

#import <Foundation/Foundation.h>

typedef void (^JYCompletionBlock)(NSData *imageData);

@interface JYConcurrentOperation2 : NSOperation

@property (nonatomic, copy) JYCompletionBlock comBlock;

@end

JYConcurrentOperation2.m

#import "JYConcurrentOperation2.h"

@interface JYConcurrentOperation2 ()<NSURLConnectionDelegate,NSURLConnectionDataDelegate>

@property (nonatomic, strong) NSURLConnection *connection;
@property (nonatomic, strong) NSMutableData *data;

@property (nonatomic, assign) CFRunLoopRef operationRunLoop;

@end

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

- (BOOL)isConcurrent{
    return YES;
}

- (void)start{
    
    if (self.isCancelled) {
        [self finish];
    }
    
    [self willChangeValueForKey:@"isExecuting"];
    _executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
    
    
    NSURL *url=[NSURL URLWithString:@"http://p1.bpimg.com/524586/79a7a2915b550222.jpg"];
    NSURLRequest * request = [NSURLRequest requestWithURL:url];
    _connection = [[NSURLConnection alloc] initWithRequest:request
                                                  delegate:self];
    
    /*
     if (![NSThread isMainThread])
     {
     [self performSelectorOnMainThread:@selector(start)
     withObject:nil
     waitUntilDone:NO];
     return;
     }
     // set up NSURLConnection...
     or
     
     [[NSOperationQueue mainQueue] addOperationWithBlock:^{
     self.connection = [NSURLConnection connectionWithRequest:self.request delegate:self];
     }];
     */
    
    NSOperationQueue *currentQueue = [NSOperationQueue currentQueue];
    BOOL backgroundQueue  = (currentQueue != nil && currentQueue != [NSOperationQueue mainQueue]);
    NSRunLoop *targetRunLoop = (backgroundQueue)?[NSRunLoop currentRunLoop]:[NSRunLoop mainRunLoop];
    
    [self.connection scheduleInRunLoop:targetRunLoop forMode:NSRunLoopCommonModes];
    [self.connection start];
    
    // make NSRunLoop stick around until operation is finished
    if (backgroundQueue) {
        self.operationRunLoop = CFRunLoopGetCurrent(); CFRunLoopRun();
    }
}

- (void)cancel{
    if (!_executing) return;
    
    [super cancel];
    [self finish];
}

- (void)finish{
    
    self.connection = nil;
    
    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];
    
    _executing = NO;
    _finished = YES;
    
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
    
    if (self.comBlock) {
        self.comBlock (_data);
    }
}

#pragma mark - NSURLConnection delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // to do something...
    self.data       = [NSMutableData data];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    // to do something...
    NSLog(@"%ld",data.length);
    [_data appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    if (self.operationRunLoop) {
       CFRunLoopStop(self.operationRunLoop);
    }
     if (self.isCancelled) return;
    
    [self finish];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [self finish];
}



@end

并發(fā)的使用:

- (void)studyNSOperation4{
    //子隊(duì)列運(yùn)行
    JYConcurrentOperation2 *op = [[JYConcurrentOperation2 alloc] init];

    op.comBlock = ^(NSData *data){
        UIImage *image = [UIImage imageWithData:data];
        self.imageView.image = image;
    };
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:op];
    
}

并發(fā)NSOperation的Demo一些解釋:

代碼中引入了RunLoop的東西->原因呢:

  • 我們把operation加到了非main queue(或者是在子線程調(diào)用的), 那么問題來了, 你會(huì)發(fā)現(xiàn)NSURLConnection delegate不走了,

  • 主線程會(huì)自動(dòng)創(chuàng)建一個(gè)RunLoop來保證程序一直運(yùn)行. 但子線程默認(rèn)不創(chuàng)建NSRunLoop, 所以子線程的任務(wù)一旦返回, 線程就over了.

  • 上面的并發(fā)operation當(dāng)start函數(shù)返回后子線程就退出了, 當(dāng)NSURLConnection的delegate回調(diào)時(shí), 線程已經(jīng)木有了, 所以你也就收不到回調(diào)了. 為了保證子線程持續(xù)live(等待connection回調(diào)), 你需要在子線程中加入RunLoop, 來保證它不會(huì)被kill掉.

結(jié)尾:

今天的NSOperation就介紹到這里缩歪,里面有不對(duì)的地方希望大神們可以提出來,今天提到了RunLoop谍憔,大家可以學(xué)習(xí)一下相關(guān)的知識(shí)點(diǎn)匪蝙。同時(shí)多線程的NSThread、GCD习贫、NSOperation在這三篇文章中基本上介紹完了逛球。

如果你喜歡請(qǐng)點(diǎn)喜歡,加關(guān)注哦_


ThreadDemo下載鏈接

iOS開發(fā)之多線程編程總結(jié)(一)
iOS開發(fā)之多線程編程總結(jié)(二)

參考資料:
http://www.cocoachina.com/ios/20150807/12911.html
http://www.reibang.com/p/ebb3e42049fd

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末苫昌,一起剝皮案震驚了整個(gè)濱河市颤绕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祟身,老刑警劉巖奥务,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異袜硫,居然都是意外死亡氯葬,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門婉陷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來帚称,“玉大人,你說我怎么就攤上這事秽澳〈扯茫” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵担神,是天一觀的道長(zhǎng)楼吃。 經(jīng)常有香客問我,道長(zhǎng)妄讯,這世上最難降的妖魔是什么孩锡? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮捞挥,結(jié)果婚禮上浮创,老公的妹妹穿的比我還像新娘。我一直安慰自己砌函,他們只是感情好斩披,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著讹俊,像睡著了一般垦沉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仍劈,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天厕倍,我揣著相機(jī)與錄音,去河邊找鬼贩疙。 笑死讹弯,一個(gè)胖子當(dāng)著我的面吹牛况既,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播组民,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼棒仍,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了臭胜?” 一聲冷哼從身側(cè)響起莫其,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耸三,沒想到半個(gè)月后乱陡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仪壮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年憨颠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睛驳。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡烙心,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乏沸,到底是詐尸還是另有隱情淫茵,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布蹬跃,位于F島的核電站匙瘪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蝶缀。R本人自食惡果不足惜丹喻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望翁都。 院中可真熱鬧碍论,春花似錦、人聲如沸柄慰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坐搔。三九已至藏研,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間概行,已是汗流浹背蠢挡。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人业踏。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓禽炬,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親勤家。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瞎抛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容