前言
前段時(shí)間的心病落下帷幕后孕锄,一大波需求向我迎來迹冤,忙的我最近沒時(shí)間更新博客了入挣,只能在閑暇的時(shí)間吹吹牛逼了嚣州。這篇博客主要講解NSOperation的一些知識(shí)鲫售。
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è)子類NSInvocationOperation
和 NSBlockOperation
或者創(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è)子類 NSInvocationOperation
和 NSBlockOperation
不能很好的滿足我們的需求時(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
, isExecuting
和isFinished
.
實(shí)現(xiàn)并發(fā)(concurrent)的NSOperation步驟:
- 重寫
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)加匈。
- 重寫
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)更清晰粘招。
- 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í)候完成了煌恢。
- 重寫
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)注哦_
iOS開發(fā)之多線程編程總結(jié)(一)
iOS開發(fā)之多線程編程總結(jié)(二)
參考資料:
http://www.cocoachina.com/ios/20150807/12911.html
http://www.reibang.com/p/ebb3e42049fd