在了解GCD之前,我們首先要知道幾個(gè)概念侠鳄。關(guān)于隊(duì)列和同/異步函數(shù)愚屁。為了讓讀者更簡(jiǎn)單直觀的理解這些概念,我盡可能用最簡(jiǎn)單的話進(jìn)行解釋絮重。
關(guān)于隊(duì)列
主隊(duì)列:專門在主線程上調(diào)度任務(wù)冤寿。
串行隊(duì)列:顧名思義,也就是任務(wù)一個(gè)接著一個(gè)的執(zhí)行(按順序執(zhí)行)青伤。
并發(fā)隊(duì)列:顧名思義督怜,任務(wù)可以同時(shí)執(zhí)行。
全局隊(duì)列:本質(zhì)上還是并發(fā)隊(duì)列狠角。
關(guān)于同/異步函數(shù)
同步函數(shù):不會(huì)開啟新線程号杠。在主線程馬上執(zhí)行任務(wù)。
異步函數(shù):會(huì)開啟新線程丰歌。不會(huì)馬上執(zhí)行任務(wù)姨蟋。
隊(duì)列和同/異步函數(shù)聯(lián)合使用
主隊(duì)列+同步函數(shù)
上面說過,開啟同步任務(wù)不會(huì)開啟新線程立帖,而是在主線程馬上執(zhí)行任務(wù)眼溶。但是主線程有正在執(zhí)行的任務(wù),只有主線程的任務(wù)執(zhí)行完畢晓勇,才能執(zhí)行主隊(duì)列中新加的任務(wù)堂飞。而主隊(duì)列也在等待主線程的任務(wù)執(zhí)行完畢灌旧,然后執(zhí)行自己的任務(wù)。
主隊(duì)列+異步函數(shù)
上面說過酝静,異步函數(shù)不會(huì)馬上執(zhí)行任務(wù)节榜,并且會(huì)開啟新線程。但是因?yàn)槭窃谥麝?duì)列中使用異步函數(shù)(因?yàn)橹麝?duì)列是在主線程上調(diào)度任務(wù)),所以既不會(huì)馬上執(zhí)行任務(wù)畸悬,也不會(huì)開啟新線程汗侵。而是等主線程的任務(wù)全部執(zhí)行完畢后,等主線程有空了再去執(zhí)行異步任務(wù)锰悼。看看代碼效果。
-(void)test {
NSLog(@"touchesBegan");
// 主隊(duì)列+異步函數(shù)
// 等主線程任務(wù)執(zhí)行完畢之后,再執(zhí)行 test2
dispatch_async(dispatch_get_main_queue(), ^{
[self test2];
});
[self test3];
NSLog(@"touchesEnd");
}
- (void)test2 {
NSLog(@"test2-----------%@",[NSThread currentThread]);
}
- (void)test3 {
NSLog(@"test3-----------%@",[NSThread currentThread]);
}
控制臺(tái)輸出:
number = 1, name = main,證明任務(wù)是在當(dāng)前線程(主線程)執(zhí)行的敞恋。
使用場(chǎng)景:
1.回到主線程更新UI
2.調(diào)整任務(wù)執(zhí)行順序
同步函數(shù)+其他隊(duì)列
串行隊(duì)列:會(huì)在當(dāng)前線程同步執(zhí)行。
并發(fā)隊(duì)列:會(huì)在當(dāng)前線程同步執(zhí)行谋右,這種使用方式?jīng)]有意義硬猫,不推薦。
異步函數(shù)+其他隊(duì)列
串行隊(duì)列:因?yàn)榇嘘?duì)列是一個(gè)執(zhí)行完后再執(zhí)行下一個(gè)改执,所以只會(huì)多開一條新線程調(diào)度任務(wù)啸蜜。
并發(fā)隊(duì)列:因?yàn)椴l(fā)隊(duì)列是多個(gè)任務(wù)同時(shí)執(zhí)行,所以會(huì)開多條新線程調(diào)度任務(wù)辈挂。
GCD
線程間通信
例:開啟一條子線程進(jìn)行下載圖片的耗時(shí)操作衬横,圖片下載完后回到主線程更新UI。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 1. 開啟子線程下載圖片
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 下載圖片!
UIImage *image = [self downloadWebImage];
// 回到主線程顯示圖片!
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"顯示圖片:%@",[NSThread currentThread]);
// 顯示圖片!
// 如果設(shè)置按鈕的圖片,必須改變按鈕的類型為 custom!
//[self.button setImage:image forState:UIControlStateNormal];
// 設(shè)置按鈕的背景圖片:
[self.button setBackgroundImage:image forState:UIControlStateNormal];
});
});
}
// 下載網(wǎng)絡(luò)圖片
// 返回值: 下載好的圖片!
-(UIImage *)downloadWebImage{
NSLog(@"downloadWebImage:%@",[NSThread currentThread]);
NSString *urlString = @"http://d.hiphotos.baidu.com/image/pic/item/64380cd7912397dd2a7d71d15d82b2b7d1a287db.jpg";
NSURL *url = [NSURL URLWithString:urlString];
// 這是一個(gè)耗時(shí)方法!這一個(gè)方法內(nèi)部封裝了很多代碼(關(guān)于網(wǎng)絡(luò)的代碼!)!
NSData *data = [NSData dataWithContentsOfURL:url];
// 下載好的圖片
UIImage *image = [UIImage imageWithData:data];
return image;
}
調(diào)度組
關(guān)鍵字: dispatch_group_t
作用:管理隊(duì)列的!
(1)參數(shù)說明
參數(shù)1:標(biāo)識(shí)隊(duì)列組!
參數(shù)2:管理的隊(duì)列!
參數(shù)3:添加到隊(duì)列中的任務(wù)!
dispatch_group_async(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)
(2)關(guān)鍵函數(shù)
等待隊(duì)列組中的任務(wù)都執(zhí)行完畢之后,就會(huì)發(fā)出通知,調(diào)用下面的方法!
隊(duì)列組: 等待哪一個(gè)隊(duì)列組中的任務(wù)執(zhí)行完畢!
隊(duì)列: 決定后續(xù)的任務(wù)在哪條線程執(zhí)行
任務(wù): 后續(xù)的任務(wù)!
dispatch_group_notify(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)
需求:下載兩張圖片终蒂,下載完成后蜂林,合并兩張圖片并顯示
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 創(chuàng)建一個(gè)隊(duì)列組!
dispatch_group_t group = dispatch_group_create();
//變量聲明
__block UIImage *image1 ,*image2;
// 下載第一張圖片
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
image1 = [self downloadImageWithUrlString:@"http://g.hiphotos.baidu.com/image/pic/item/95eef01f3a292df54e0e7e08be315c6035a873da.jpg"];
});
// 下載第二張圖片
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
image2 = [self downloadImageWithUrlString:@"http://e.hiphotos.baidu.com/image/pic/item/cc11728b4710b912d4bb69ffc1fdfc03924522bc.jpg"];
});
// 合并圖片并且顯示
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 合并圖片
UIImage *image = [self bingImageWithImage1:image1 Image2:image2];
// 顯示合并之后的圖片!
self.imageView.image = image;
});
}
// 合并圖片
-(UIImage *)bingImageWithImage1:(UIImage *)image1 Image2:(UIImage *)image2{
// 1.開啟圖形上下文
UIGraphicsBeginImageContext(self.imageView.bounds.size);
//2.繪制第一張圖片
[image1 drawInRect:self.imageView.bounds];
// 3.繪制第二張圖片
[image2 drawInRect:CGRectMake(0, self.imageView.bounds.size.height - 80, self.imageView.bounds.size.width, 80)];
// 4.獲取繪制好的圖片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 5.關(guān)閉圖形上下文
UIGraphicsEndImageContext();
return image;
}
// 下載圖片
-(UIImage *)downloadImageWithUrlString:(NSString *)urlString{
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
return image;
}
一次性代碼
使用場(chǎng)景:?jiǎn)卫K_保代碼只被執(zhí)行一次拇泣,不管有多少個(gè)線程噪叙。GCD的dispatch_once可以確保代碼只被執(zhí)行一次。
@interface Person : NSObject
+ (instancetype)sharedPerson;
@end
@implementation Person
+ (instancetype)sharedPerson{
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
@end
阻塞式函數(shù)
使用場(chǎng)景:任務(wù)1和任務(wù)2同時(shí)執(zhí)行霉翔,然后執(zhí)行任務(wù)3睁蕾,然后任務(wù)4和任務(wù)5同時(shí)執(zhí)行。
需要注意:阻塞式函數(shù)只有自己創(chuàng)建的并發(fā)隊(duì)列才有用早龟,對(duì)全局并發(fā)隊(duì)列無效惫霸。
關(guān)鍵字: dispatch_barrier_async
- (void)test {
// 需求:任務(wù) 1~5 ,執(zhí)行順序:
// 第三個(gè)任務(wù)必須等待前兩個(gè)任務(wù)執(zhí)行完畢之后,再執(zhí)行!
// 后兩個(gè)任務(wù)必須等待第三個(gè)任務(wù)完成之后再執(zhí)行!
// 任務(wù)1和2 同時(shí)執(zhí)行, 任務(wù)4和5同時(shí)執(zhí)行!
// 阻塞式函數(shù)! -- 主要是阻塞并發(fā)隊(duì)列的!(只有自己創(chuàng)建的并發(fā)隊(duì)列才有效,全局并發(fā)隊(duì)列不可以使用阻塞式函數(shù)).
// 1.隊(duì)列:需要阻塞哪一個(gè)隊(duì)列
// 2.任務(wù):阻塞隊(duì)列的任務(wù)! 任務(wù)3/阻塞式任務(wù)!
// dispatch_barrier_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
// 創(chuàng)建并發(fā)隊(duì)列,按順序往并發(fā)隊(duì)列中添加任務(wù)!
// 創(chuàng)建一個(gè)并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
// 按順序往并發(fā)隊(duì)列中添加任務(wù)
dispatch_async(queue, ^{
//模擬耗時(shí)操作
[NSThread sleepForTimeInterval:5];
NSLog(@"任務(wù)1:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
//模擬耗時(shí)操作
[NSThread sleepForTimeInterval:3];
NSLog(@"任務(wù)2:%@",[NSThread currentThread]);
});
// 任務(wù)3是一個(gè)阻塞式任務(wù),利用阻塞式函數(shù)添加
// 同步和異步都是相對(duì)于當(dāng)前線程來說的!
dispatch_barrier_sync(queue, ^{
//模擬耗時(shí)操作
[NSThread sleepForTimeInterval:5];
NSLog(@"任務(wù)3:%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:5];
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)4:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)5:%@",[NSThread currentThread]);
});
}
延時(shí)執(zhí)行
這里不止有GCD的延時(shí)執(zhí)行方法。在了解這兩個(gè)延時(shí)執(zhí)行方法之前我們還是要了解一些和Runloop有關(guān)的概念性知識(shí)葱弟,因?yàn)檫@很有必要壹店。當(dāng)然只是做一些簡(jiǎn)單的介紹,深入的介紹會(huì)在以后陸續(xù)講解芝加。
1硅卢、主線程和子線程的區(qū)別
主線程的運(yùn)行循環(huán)默認(rèn)是開啟的,子線程的運(yùn)行循環(huán)默認(rèn)是關(guān)閉的!
2射窒、Runloop
運(yùn)行循環(huán)驅(qū)動(dòng)(執(zhí)行)事件源(UI操作/點(diǎn)擊/滾動(dòng)/定時(shí)器/特殊的事件)。
運(yùn)行循環(huán)是一個(gè)死循環(huán)(do...while...循環(huán))!平時(shí)沒有事件驅(qū)動(dòng)的時(shí)候,運(yùn)行循環(huán)就處于睡眠狀態(tài)!當(dāng)有一些事件源發(fā)生的時(shí)候,就會(huì)喚醒運(yùn)行循環(huán)來執(zhí)行事件!事件執(zhí)行完畢之后,運(yùn)行循環(huán)接著進(jìn)入睡眠狀態(tài)!
運(yùn)行循環(huán)的開啟需要事件源的驅(qū)動(dòng)!一個(gè)線程中,如果沒有事件源,運(yùn)行循環(huán)無法開啟!
3将塑、兩種延遲執(zhí)行的方式
(1) GCD
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
<#code to be executed after a specified delay#>
});
(2) other
self performSelector:<#(nonnull SEL)#> withObject:<#(nullable id)#> afterDelay:<#(NSTimeInterval)#>
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touchesBegan");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(test) withObject:nil afterDelay:3.0];
NSLog(@"能夠到這里嗎?");
});
NSLog(@"touchesEnd");
}
- (void)test {
NSLog(@"test");
}
輸出結(jié)果:
通過控制臺(tái)輸出可以看到test方法的Log并沒有打印脉顿。
為什么呢?
方式(2)是一個(gè)事件源点寥,一個(gè)特殊的事件源艾疟,這句代碼執(zhí)行完后,會(huì)自動(dòng)關(guān)閉運(yùn)行循環(huán)敢辩。
需要延遲執(zhí)行的test方法并沒有執(zhí)行,因?yàn)檫@句代碼執(zhí)行完后就關(guān)閉了運(yùn)行循環(huán)蔽莱。
所以為了延遲執(zhí)行test方法,需要在執(zhí)行完這句代碼后開啟運(yùn)行循環(huán)戚长,代碼 [[NSRunLoop currentRunLoop] run];
[[NSRunLoop currentRunLoop] run]最好寫成 CFRunLoopRun();這樣寫方便我們關(guān)閉它盗冷。
因?yàn)镹SRunLoop并沒有為我們提供關(guān)閉它的方法,而 CFRunLoopRun()給我們提供了關(guān)閉運(yùn)行循環(huán)的方法,如下
CFRunLoopGetCurrent():獲得當(dāng)前線程的運(yùn)行循環(huán)!
CFRunLoopStop(CFRunLoopGetCurrent()); 關(guān)閉運(yùn)行循環(huán)!
所以還要注意同廉,如果要在一個(gè)子線程中開啟一個(gè)定時(shí)器仪糖,把當(dāng)前定時(shí)器添加到運(yùn)行循環(huán)后,一定要開啟運(yùn)行循環(huán)迫肖。(因?yàn)樽泳€程的運(yùn)行循環(huán)默認(rèn)是關(guān)閉的)锅劝。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touchesBegan");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//創(chuàng)建一個(gè)定時(shí)器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
//把定時(shí)器加入到當(dāng)前的運(yùn)行循環(huán)
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//開啟運(yùn)行循環(huán)
CFRunLoopRun();
NSLog(@"come here! %@",[NSThread currentThread]);
});
NSLog(@"touchesEnd");
}
- (void)test {
static int i = 0;
NSLog(@" i = %d",i);
if (i == 6) {
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"關(guān)閉運(yùn)行循環(huán)!");
}
}
控制臺(tái)輸出:
NSOperation & NSOperationQueue
NSOperation是對(duì)GCD的封裝,面向操作編程咒程。
NSOperation有兩種方式創(chuàng)建操作對(duì)象
(1)
NSInvocationOperation *opInvo = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
(2)
NSBlockOperation *opBlock = [NSBlockOperation blockOperationWithBlock:^{
//xxxx
}];
NSOperation的執(zhí)行
第一種情況:
將操作添加到隊(duì)列中
[queue addOperation:op];
使用這種方式向隊(duì)列中添加操作鸠天,所有操作都會(huì)在子線程中執(zhí)行。
所以帐姻,建議如果NSBlockOperation需要在主線程執(zhí)行稠集,就不要追加操作。
第二種情況:
調(diào)用start方法
[op start];
NSBlockOperation 如果追加了任務(wù)! 直接調(diào)用 start 方法或者將操作添加到主隊(duì)列, 這個(gè)時(shí)候,操作中的任務(wù)會(huì)在不同的線程執(zhí)行(主線程 + 子線程)饥瓷。
兩種隊(duì)列
主隊(duì)列:在主線程中完成操作
非主隊(duì)列:在子線程中完成操作
(1)創(chuàng)建主隊(duì)列
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
// 添加操作到隊(duì)列中!
// 將操作添加到隊(duì)列中之后,就會(huì)自動(dòng)執(zhí)行 NSOperation 對(duì)象的 main 方法!
[queue1 addOperation:op];
(2)創(chuàng)建非主隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 添加操作到隊(duì)列中!
// 將操作添加到隊(duì)列中之后,就會(huì)自動(dòng)執(zhí)行 NSOperation 對(duì)象的 main 方法!
[queue addOperation:op];
(3)往隊(duì)列中添加操作的簡(jiǎn)便寫法
// 缺點(diǎn): 沒有一個(gè)操作對(duì)象.不能對(duì)操作做后續(xù)的管理!
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"任務(wù):%@",[NSThread currentThread]);
}];
NSBlockOperation追加任務(wù)及需要注意的點(diǎn)
- 創(chuàng)建一個(gè)操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)1 %@",[NSThread currentThread]);
}];
2.NSBlockOperation 可以追加任務(wù)!
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op1 - %@",[NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"任務(wù)2 %@",[NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"任務(wù)3 %@",[NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"任務(wù)4 %@",[NSThread currentThread]);
}];
[op start];
控制臺(tái)輸出:
也就是說追加的任務(wù)全部是在子線程中執(zhí)行的剥纷!
NSOperation的高級(jí)使用方法
操作依賴
比如有如下需求:
有5個(gè)操作1-5,操作1-3在子線程中執(zhí)行呢铆,操作4-5在主線程中執(zhí)行晦鞋,并且操作的執(zhí)行順序是1,2棺克,3悠垛,4,5娜谊。這個(gè)時(shí)候就需要使用操作依賴确买。讓后一個(gè)操作依賴前一個(gè)操作,并且操作依賴可以夸隊(duì)列(線程)纱皆。
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)1 %@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)2 %@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)3 %@",[NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)4 %@",[NSThread currentThread]);
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)5 %@",[NSThread currentThread]);
}];
// 創(chuàng)建非主隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 保證操作按順序執(zhí)行 --- 添加操作依賴!
// 能夠保證先執(zhí)行 op2 ,再執(zhí)行 op1
// 操作依賴內(nèi)部使用了:線程同步技術(shù)!
// 添加操作依賴注意:
// 注意1: 不要添加循環(huán)依賴!
// 注意2: 一定要先添加操作依賴,然后再把操作添加到操作隊(duì)列中!
// 注意3: 對(duì)不不同隊(duì)列中的操作,添加操作依賴依然有效!
[op2 addDependency:op1];
[op3 addDependency:op2];
[op4 addDependency:op3];
[op5 addDependency:op4];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
NSOperationQueue *queue2 = [NSOperationQueue mainQueue];
[queue2 addOperation:op4];
[queue2 addOperation:op5];
控制臺(tái)的輸出一定是按照順序輸出的!
隊(duì)列操作
操作隊(duì)列可以管理操作湾趾,可以 暫停/恢復(fù)/取消操作芭商, 實(shí)際開發(fā)中,為了在任何時(shí)候都能夠使用這個(gè)隊(duì)列,做成全局的!
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 暫停隊(duì)列中的操作
[queue setSuspended:YES];
// 恢復(fù)隊(duì)列中的操作
[queue setSuspended:NO];
// 取消隊(duì)列中的所有操作
// 取消操作:對(duì)于已經(jīng)開始的操作是無法取消的!
// 系統(tǒng)接收到內(nèi)存警告的時(shí)候!
[queue cancelAllOperations];
// 取消單個(gè)操作!
[op cancel];
隊(duì)列間線程通信
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
// 1.異步下載圖片
NSURL *url = [NSURL URLWithString:@"http://d.hiphotos.baidu.com/image/pic/item/37d3d539b6003af3290eaf5d362ac65c1038b652.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 2.回到主線程,顯示圖片
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
}];
如何選擇使用GCD還是NSOperation ?
首先搀缠,NSOperation是對(duì)GCD的封裝铛楣,在使用上當(dāng)然是GCD更具有效率。
NSOperation是面向操作編程艺普,GCD是面向任務(wù)編程簸州。
使用NSOperation我們可以將某個(gè)操作暫停,恢復(fù)或取消操作衷敌∥鸷睿可以滿足許多使用場(chǎng)景,比如一個(gè)tableView上下滾動(dòng)的時(shí)候缴罗,如果開始滾動(dòng)就暫停下載操作,如果停止?jié)L動(dòng)就恢復(fù)下載操作祭埂。如果系統(tǒng)提示內(nèi)存警告還可以取消操作面氓。
但是GCD就不具備NSOperation的上述功能,GCD適用一些簡(jiǎn)單的需求,比如簡(jiǎn)單的多線程操作等等蛆橡。
所以根據(jù)具體需求選擇相應(yīng)的實(shí)現(xiàn)技術(shù)舌界。