一、簡(jiǎn)介
除了次泽,NSThread和GCD實(shí)現(xiàn)多線(xiàn)程,配合使用NSOperation和NSOperationQueue也能實(shí)現(xiàn)多線(xiàn)程編程
NSOperation和NSOperationQueue實(shí)現(xiàn)多線(xiàn)程的具體步驟
1席爽、先將需要執(zhí)行的操作封裝到一個(gè)NSOperation的子類(lèi)對(duì)象中
實(shí)際上意荤,NSOperation是個(gè)抽象類(lèi),并不具備封裝操作的能力,必須使用它的子類(lèi)
2只锻、然后將NSOperation對(duì)象添加到NSOperationQueue中
3玖像、系統(tǒng)會(huì)自動(dòng)將NSOperationQueue中的NSOperation取出來(lái)
4、將取出的NSOperation封裝的操作放到一條新線(xiàn)程中執(zhí)行
如上所示:要實(shí)現(xiàn)多線(xiàn)程捐寥,必須要將執(zhí)行的操作封裝到NSOperation的子類(lèi)對(duì)象中,那么NSOperation的子類(lèi)有哪些沈矿?
1上真、使用NSOperation子類(lèi)的方式有3種
NSInvocationOperation
NSBlockOperation
自定義子類(lèi)繼承NSOperation咬腋,實(shí)現(xiàn)NSOperation的某些方法
創(chuàng)建NSInvocationOperation對(duì)象
-(id)initWithTarget:(id)target selector:(SEL)selector object:(id)arg;
NSInvocationOperation將操作封裝成SEL羹膳,只有將NSInvocationOperation添加到NSOperationQueue中,才會(huì)異步執(zhí)行操作根竿。
- (void)test {
// 1.將操作封裝到Operation中
NSInvocationOperation*op1 = [[NSInvocationOperationalloc] initWithTarget:selfselector:@selector(demo) object:nil];
// 2.執(zhí)行封裝的操作
// 如果直接執(zhí)行NSInvocationOperation中的操作, 那么默認(rèn)會(huì)在主線(xiàn)程中執(zhí)行
//[op1 start];
// 創(chuàng)建隊(duì)列
NSOperationQueue*queue = [[NSOperationQueuealloc] init];
//將任務(wù)加入到隊(duì)列 任務(wù)會(huì)自動(dòng)開(kāi)啟
[queue addOperation:op1];
}
創(chuàng)建NSBlockOperation 對(duì)象
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
NSBlockOperation將任務(wù)封裝成一個(gè)block陵像,這種方式看起來(lái)更方便就珠。當(dāng)然,你還可以調(diào)用-addExecutionBlock:方法醒颖,添加另一個(gè)任務(wù)妻怎。
注意點(diǎn):只要NSBlockOperation封裝的操作數(shù) >1,直接調(diào)用start方法泞歉,就會(huì)異步執(zhí)行操作逼侦,但是默認(rèn)的初始化的block任務(wù)是在主線(xiàn)程中執(zhí)行。
- (void)test {
NSBlockOperation*blockOperation = [NSBlockOperationblockOperationWithBlock:^{
NSLog(@"開(kāi)始執(zhí)行任務(wù)");
}];
[blockOperation addExecutionBlock:^{
NSLog(@"開(kāi)始下一個(gè)執(zhí)行任務(wù)");
}];
//如果不將NSBlockOperation添加到隊(duì)列中腰耙,而是直接調(diào)用start方法榛丢,那么默認(rèn)初始化時(shí)的任務(wù)執(zhí)行在主線(xiàn)程中画机,而通過(guò)addExecutionBlock方法添加的任務(wù)都執(zhí)行在子線(xiàn)程中呛谜。
//[blockOperation start];
// 創(chuàng)建隊(duì)列
NSOperationQueue*queue = [[NSOperationQueuealloc] init];
//將任務(wù)加入到隊(duì)列 任務(wù)會(huì)自動(dòng)開(kāi)啟
[queue addOperation: blockOperation];
}
首先需要繼承NSOperation嗦明,然后實(shí)現(xiàn)某些方法蚣抗。
我們有兩種方法來(lái)實(shí)現(xiàn)自定義operation侯繁,一種是通過(guò)重寫(xiě)main方法鱼鼓,一種是通過(guò)重寫(xiě)start方法帮寻,前者實(shí)現(xiàn)起來(lái)更簡(jiǎn)單悍赢,我們不需要管理一些屬性狀態(tài)援制,例如isExecuting或者isFinished等戏挡。當(dāng)main方法執(zhí)行結(jié)束的時(shí)候,這個(gè)operation就結(jié)束了晨仑。另外一種是重寫(xiě)start方法增拥,但是這種方法需要你管理各種屬性的狀態(tài),雖然麻煩寻歧,但是相對(duì)靈活掌栅。如果你同時(shí)實(shí)現(xiàn)start和main方法,則優(yōu)先使用start方法码泛。因?yàn)榈谝环N方法比較簡(jiǎn)單猾封,我們這里以第二種方法來(lái)自定義operation。
首先噪珊,父類(lèi)提供了一系列的getter方法晌缘,用以獲知任務(wù)的狀態(tài)。
@property (readonly, getter=isReady) BOOL ready
此屬性表明NSOperation是否已經(jīng)準(zhǔn)備好開(kāi)始執(zhí)行任務(wù)了痢站,如果返回false磷箕,NSOperation的之后的方法都不會(huì)執(zhí)行。通常情況下阵难,自定義operation岳枷,我們可以利用它來(lái)讓調(diào)用者必須滿(mǎn)足一定的條件才能執(zhí)行任務(wù)。
@property (readonly, getter=isExecuting) BOOL executing
是否正在執(zhí)行。
@property (readonly, getter=isFinished) BOOL finished
任務(wù)是否完成
@property (readonly, getter=isCancelled) BOOL cancelled
任務(wù)是否取消
@property (readonly, getter=isConcurrent) BOOL concurrent
表示是否異步執(zhí)行任務(wù)
自定義operation如下
MyOperation.h
@interfaceMyOperation:NSOperation
+ (instancetype)crateOperationWithMoney:(int)money;
@end
MyOperation.m
#import"MyOperation.h"
#import
@interfaceMyOperation()
@property(nonatomic,assign)intmoney;
@property(nonatomic,assign)BOOLcancelled;
@property(nonatomic,assign)BOOLexecuting;
@property(nonatomic,assign)BOOLfinished;
@property(nonatomic,assign)BOOLconcurrent;
@property(nonatomic,assign)BOOLasynchronous;
@end
@implementationMyOperation
@synthesizecancelled;
@synthesizeexecuting;
@synthesizefinished;
@synthesizeconcurrent;
@synthesizeasynchronous;
+ (instancetype)crateOperationWithMoney:(int)money {
MyOperation *operation = [[MyOperation alloc] init];
operation.money = money;
returnoperation;
}
- (BOOL)isReady {
//只有當(dāng)金額大于等于100的時(shí)候才執(zhí)行任務(wù)
return_money >=100;
}
- (void)start {
//判斷任務(wù)狀態(tài)
if(self.isExecuting ||self.isCancelled ||self.isFinished) {
return;
}
NSLog(@"%@--%@開(kāi)始花錢(qián)",self.name,[NSThreadcurrentThread]);
self.executing =YES;
while(self.money >0){
self.money--;
NSLog(@"%@花錢(qián)了%d",self.name,self.money);
}
//執(zhí)行完畢
self.finished =YES;
self.executing =NO;
}
- (BOOL)isExecuting {
returnself.executing;
}
- (void)cancel {
NSLog(@"%@%@取消",[NSThreadcurrentThread],self.name);
self.finished =YES;
self.cancelled =YES;
self.executing =NO;
}
- (BOOL)isCancelled {
returnself.cancelled;
}
- (BOOL)isFinished {
returnself.finished;
}
- (BOOL)isConcurrent {
returnself.concurrent;
}
- (BOOL)isAsynchronous {
returnself.asynchronous;
}
- (void)dealloc {
NSLog(@"%@dealloc",self.name);
}
@end
這僅僅是一個(gè)簡(jiǎn)單的實(shí)例空繁,真正的項(xiàng)目中還需要打磨殿衰,如果沒(méi)有特殊的要求,還是使用系統(tǒng)提供的比較好盛泡。
目的 -> NSOperation之間可以設(shè)置依賴(lài)來(lái)保證執(zhí)行順序
例如:一定要讓操作A執(zhí)行完后闷祥,才能執(zhí)行操作B,可以這么寫(xiě)
[operationB addDependency:operationA];
例如我們通過(guò)不同的線(xiàn)程下載圖片 當(dāng)都下載完成以后在合成圖片
#import"ViewController.h"
@interfaceViewController()
@property(weak,nonatomic)IBOutletUIImageView*imageView;
@end
@implementationViewController
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
NSOperationQueue*queue = [[NSOperationQueuealloc] init];
NSOperationQueue*queue2 = [[NSOperationQueuealloc] init];
__blockUIImage*image1 =nil;
__blockUIImage*image2 =nil;
// 1.開(kāi)啟一個(gè)線(xiàn)程下載第一張圖片
NSOperation*op1 = [NSBlockOperationblockOperationWithBlock:^{
NSURL*url = [NSURLURLWithString:@"http://cdn.cocimg.com/assets/images/logo.png?v=201510272"];
NSData*data = [NSDatadataWithContentsOfURL:url];
// 2.生成下載好的圖片
UIImage*image = [UIImageimageWithData:data];
image1 = image;
}];
// 2.開(kāi)啟一個(gè)線(xiàn)程下載第二長(zhǎng)圖片
NSOperation*op2 = [NSBlockOperationblockOperationWithBlock:^{
NSURL*url = [NSURLURLWithString:@"https://www.baidu.com/img/bd_logo1.png"];
NSData*data = [NSDatadataWithContentsOfURL:url];
// 2.生成下載好的圖片
UIImage*image = [UIImageimageWithData:data];
image2 = image;
}];
// 3.開(kāi)啟一個(gè)線(xiàn)程合成圖片
NSOperation*op3 = [NSBlockOperationblockOperationWithBlock:^{
UIGraphicsBeginImageContext(CGSizeMake(200,200));
[image1 drawInRect:CGRectMake(0,0,100,200)];
[image2 drawInRect:CGRectMake(100,0,100,200)];
UIImage*newImage =UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 4.回到主線(xiàn)程更新UI
[[NSOperationQueuemainQueue] addOperationWithBlock:^{
NSLog(@"回到主線(xiàn)程更新UI");
self.imageView.image = newImage;
}];
}];
// 監(jiān)聽(tīng)任務(wù)是否執(zhí)行完畢
op1.completionBlock = ^{
NSLog(@"第一張圖片下載完畢");
};
op2.completionBlock = ^{
NSLog(@"第二張圖片下載完畢");
};
// 添加依賴(lài)
// 只要添加了依賴(lài), 那么就會(huì)等依賴(lài)的任務(wù)執(zhí)行完畢, 才會(huì)執(zhí)行當(dāng)前任務(wù)
// 注意:
// 1.添加依賴(lài), 不能添加循環(huán)依賴(lài)
// 2.NSOperation可以跨隊(duì)列添加依賴(lài)
[op3 addDependency:op1];
[op3 addDependency:op2];
// 將任務(wù)添加到隊(duì)列中
[queue addOperation:op1];
[queue addOperation:op2];
[queue2 addOperation:op3];
}
@end
NSOperation可以調(diào)用start方法來(lái)執(zhí)行操作凯砍,但是默認(rèn)是同步執(zhí)行的。將NSOperation添加到NSOperationQueue中拴竹,系統(tǒng)會(huì)自動(dòng)異步調(diào)用NSOperation的start方法或者main方法來(lái)執(zhí)行任務(wù)果覆。
NSOperationQueue提供了三種添加任務(wù)的方法:
- (void)addOperation:(NSOperation *)op將NSOperation對(duì)象添加到隊(duì)列中
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait將NSOperation對(duì)象數(shù)組添加到隊(duì)列中,wait表示是否阻塞當(dāng)前隊(duì)列殖熟,YES表明以后添加的任務(wù)只能在ops中的所有任務(wù)執(zhí)行完成以后才會(huì)執(zhí)行局待,NO表示不阻塞當(dāng)前隊(duì)列
- (void)addOperationWithBlock:(void (^)(void))block將一個(gè)block添加到任務(wù)中。
這里有幾點(diǎn)需要注意的菱属,添加的任務(wù)一定不能是正在執(zhí)行的或者已經(jīng)完成的钳榨,可通過(guò)executing和finished來(lái)判斷。不能重復(fù)添加一個(gè)operation到同一個(gè)隊(duì)列中纽门,否則會(huì)報(bào)NSInvalidArgumentException異常薛耻。
再來(lái)看看NSOperationQueue的屬性:
operations當(dāng)前正在執(zhí)行或者等待執(zhí)行的任務(wù)的集合,如果一個(gè)任務(wù)執(zhí)行完畢赏陵,會(huì)從中刪除饼齿。
operationCount當(dāng)前隊(duì)列中的任務(wù)數(shù)(正在執(zhí)行或者等待執(zhí)行)
maxConcurrentOperationCount最大并發(fā)數(shù),默認(rèn)是-1蝙搔,即無(wú)限大缕溉,如果設(shè)置為1,那么當(dāng)前隊(duì)列即為串行隊(duì)列吃型,但不能設(shè)置成0证鸥,如果設(shè)置成0,那么當(dāng)前的所有任務(wù)都不能執(zhí)行勤晚。
name隊(duì)列的名字
當(dāng)然枉层,NSOperationQueue也提供了取消操作,
- (void)cancelAllOperations赐写,此方法會(huì)取消所有未執(zhí)行的操作鸟蜡。內(nèi)部實(shí)現(xiàn)是分別調(diào)用任務(wù)的cancel方法。但是已經(jīng)執(zhí)行的操作將會(huì)繼續(xù)執(zhí)行挺邀。另外此方法并不會(huì)將取消的操作從隊(duì)列中移除揉忘。但是實(shí)際卻移除了跳座,這是因?yàn)镹SInvocationOperation和NSBlockOperation內(nèi)部自己管理了finish狀態(tài),在cancel方法中會(huì)將當(dāng)前任務(wù)的finish設(shè)置為YES癌淮。
- (void)waitUntilAllOperationsAreFinished阻塞當(dāng)前任務(wù)隊(duì)列躺坟。
NSOperation的作用
配合使用NSOperation和NSOperationQueue也能實(shí)現(xiàn)多線(xiàn)程編程
NSOperation和NSOperationQueue實(shí)現(xiàn)多線(xiàn)程的具體步驟
先將需要執(zhí)行的操作封裝到一個(gè)NSOperation對(duì)象中
然后將NSOperation對(duì)象添加到NSOperationQueue中
系統(tǒng)會(huì)自動(dòng)將NSOperationQueue中的NSOperation取出來(lái)
將取出的NSOperation封裝的操作放到一條新線(xiàn)程中執(zhí)行
NSOperation的子類(lèi)
NSOperation是個(gè)抽象類(lèi)沦补,并不具備封裝操作的能力乳蓄,必須使用它的子類(lèi)
使用NSOperation子類(lèi)的方式有3種
NSInvocationOperation
NSBlockOperation
自定義子類(lèi)繼承NSOperation,實(shí)現(xiàn)內(nèi)部相應(yīng)的方法
創(chuàng)建NSInvocationOperation對(duì)象
-(id)initWithTarget:(id)targetselector:(SEL)selobject:(id)arg;
調(diào)用start方法開(kāi)始執(zhí)行操作
-(void)start;
一旦執(zhí)行操作夕膀,就會(huì)調(diào)用target的sel方法
注意
默認(rèn)情況下虚倒,調(diào)用了start方法后并不會(huì)開(kāi)一條新線(xiàn)程去執(zhí)行操作,而是在當(dāng)前線(xiàn)程同步執(zhí)行操作
只有將NSOperation放到一個(gè)NSOperationQueue中产舞,才會(huì)異步執(zhí)行操作
創(chuàng)建NSBlockOperation對(duì)象
+(id)blockOperationWithBlock:(void(^)(void))block;
通過(guò)addExecutionBlock:方法添加更多的操作
-(void)addExecutionBlock:(void(^)(void))block;
注意:只要NSBlockOperation封裝的操作數(shù) >1魂奥,就會(huì)異步執(zhí)行操作
NSOperationQueue的作用
NSOperation可以調(diào)用start方法來(lái)執(zhí)行任務(wù),但默認(rèn)是同步執(zhí)行的
如果將NSOperation添加到NSOperationQueue(操作隊(duì)列)中易猫,系統(tǒng)會(huì)自動(dòng)異步執(zhí)行NSOperation中的操作
添加操作到NSOperationQueue中
-(void)addOperation:(NSOperation*)op;
-(void)addOperationWithBlock:(void(^)(void))block;
什么是并發(fā)數(shù)
同時(shí)執(zhí)行的任務(wù)數(shù)
比如耻煤,同時(shí)開(kāi)3個(gè)線(xiàn)程執(zhí)行3個(gè)任務(wù),并發(fā)數(shù)就是3
最大并發(fā)數(shù)的相關(guān)方法
-(NSInteger)maxConcurrentOperationCount;
-(void)setMaxConcurrentOperationCount:(NSInteger)cat;
取消隊(duì)列的所有操作
-(void)cancelAllOperations;
提示:也可以調(diào)用NSOperation的-(void)cancel方法取消單個(gè)操作
暫停和恢復(fù)隊(duì)列
-(void)setSuspended:(BOOL)b;// YES代表暫停隊(duì)列准颓,NO代表恢復(fù)隊(duì)列
-(BOOL)isSuspended;
Operation之間可以設(shè)置依賴(lài)來(lái)保證執(zhí)行順序
比如一定要讓操作A執(zhí)行完后哈蝇,才能執(zhí)行操作B,可以這么寫(xiě)
[operationBaddDependency:operationA];// 操作B依賴(lài)于操作A
可以在不同queue的NSOperation之間創(chuàng)建依賴(lài)關(guān)系
可以監(jiān)聽(tīng)一個(gè)操作的執(zhí)行完畢
-(void(^)(void))completionBlock;
-(void)setCompletionBlock:(void(^)(void))block;
自定義NSOperation的步驟很簡(jiǎn)單
重寫(xiě)-(void)main方法攘已,在里面實(shí)現(xiàn)想執(zhí)行的任務(wù)
重寫(xiě)-(void)main方法的注意點(diǎn)
自己創(chuàng)建自動(dòng)釋放池(因?yàn)槿绻钱惒讲僮髋谏猓瑹o(wú)法訪(fǎng)問(wèn)主線(xiàn)程的自動(dòng)釋放池)
經(jīng)常通過(guò)-(BOOL)isCancelled方法檢測(cè)操作是否被取消,對(duì)取消做出響應(yīng)
一個(gè)操作需要取消時(shí)样勃,必須等這個(gè)操作的任務(wù)執(zhí)行完畢吠勘,或者在這個(gè)操作的內(nèi)容不斷的判斷是否有取消操作。
總結(jié):
回主線(xiàn)刷新UI
[[NSOperationQueue mainQueue] addOprationWithBblock:^{回主線(xiàn)程刷新UI}];
------------------------------------------------------------
操作:NSOperation峡眶、NSInvocationOperation剧防、NSBlockOpearation。
隊(duì)列:NSOperationQueue
當(dāng)使用NSInvocationOperation時(shí)
操作加入隊(duì)列時(shí):(注意:當(dāng)前線(xiàn)程決定currentQueue在主隊(duì)列還是在子隊(duì)列)
1辫樱、不需要調(diào)用start方法诵姜,如果調(diào)用start方法會(huì)崩潰。
2搏熄、在主隊(duì)列時(shí)(和當(dāng)前線(xiàn)程無(wú)關(guān))在主線(xiàn)程執(zhí)行
3棚唆、在自定義隊(duì)列時(shí)=> [[NSOperationQueue alloc]init](和當(dāng)前線(xiàn)程無(wú)關(guān))在子線(xiàn)程中執(zhí)行
不加入隊(duì)列時(shí):
1、需要調(diào)用start方法心例,在當(dāng)前線(xiàn)程中執(zhí)行(主=主宵凌,子=子)
所以:隊(duì)列決定了線(xiàn)程在主線(xiàn)程還是子線(xiàn)程中執(zhí)行
所在隊(duì)列執(zhí)行線(xiàn)程.png
當(dāng)是用NSBlockOperation時(shí)
不加入隊(duì)列,:需要調(diào)用start方法
直接用NSBlockOperation封裝的任務(wù)會(huì)在當(dāng)前線(xiàn)程中執(zhí)行(主=主止后,子=子)瞎惫。
當(dāng)使用addExecutionBlock添加任務(wù)后溜腐,也就是當(dāng)任務(wù)的個(gè)數(shù)大于1,那么之后的任務(wù)會(huì)在子線(xiàn)程中執(zhí)行瓜喇,NSBlockOperation封裝的任務(wù)還是會(huì)在主線(xiàn)程中執(zhí)行挺益。
加入隊(duì)列:不需要調(diào)用start方法
會(huì)在子線(xiàn)程中執(zhí)行任務(wù)。
NSINvocationOperation和NSBlockOpertaion和自定義的NSOperation都可以同時(shí)加入到同一個(gè)隊(duì)列中乘寒,不需要調(diào)用start方法望众,并且都是在子線(xiàn)程中執(zhí)行。
自定義NSOPeration需要將操作封裝到自定的這個(gè)對(duì)象中伞辛,通過(guò)重寫(xiě)mian方法來(lái)實(shí)現(xiàn)封裝任務(wù)烂翰。
異步串行執(zhí)行任務(wù)的方式
NSOpration實(shí)現(xiàn)異步串行執(zhí)行的方式:
1、設(shè)置最大并發(fā)數(shù)為1蚤氏。
2甘耿、不設(shè)置并發(fā)數(shù)時(shí),設(shè)置任務(wù)之間的依賴(lài)竿滨。
而GCD實(shí)現(xiàn)異步串行執(zhí)行的方式:
1佳恬、異步+自定義串行隊(duì)列(非主隊(duì)列)
2、dispatch_barrier_async柵欄函數(shù)
NSOperation比GCD的好處
1于游、能通過(guò)KVO鍵值觀察者來(lái)實(shí)時(shí)監(jiān)控operation的狀態(tài)(是否執(zhí)行毁葱,是否取消),而GCD無(wú)法通過(guò)KVO來(lái)實(shí)時(shí)監(jiān)控曙砂。通過(guò)completBlock來(lái)回調(diào)已經(jīng)執(zhí)行完畢头谜。
2、NSOperation能通過(guò)設(shè)置依賴(lài)鸠澈,使任務(wù)之間有先后順序柱告。GCD可以通過(guò)柵欄函數(shù)來(lái)實(shí)現(xiàn)。
3笑陈、NSOperation能設(shè)置并行隊(duì)列中任務(wù)(NSOperation)的優(yōu)先級(jí)(設(shè)置NSOperation在queue中的優(yōu)先級(jí),可以改變操作的執(zhí)?優(yōu)先級(jí))(優(yōu)先級(jí)的作用是讓優(yōu)先級(jí)高的線(xiàn)程需要cpu時(shí)就能夠得到cpu际度,而不是讓優(yōu)先級(jí)低的線(xiàn)程無(wú)法運(yùn)行,并且可能不止一個(gè)CPU,說(shuō)明:優(yōu)先級(jí)高的任務(wù)涵妥,調(diào)用的幾率會(huì)更大)
NSOperation是將任務(wù)放入隊(duì)列中來(lái)執(zhí)行的乖菱。
GCD是將任務(wù)放入隊(duì)列中,通過(guò)同步異步函數(shù)來(lái)執(zhí)行(可以多個(gè)同步異步函數(shù))蓬网。
而GCD是設(shè)置不同任務(wù)隊(duì)列(queue)的優(yōu)先級(jí)窒所,要實(shí)現(xiàn)block的優(yōu)先級(jí),需要很多代碼帆锋。
GCD更簡(jiǎn)潔