一涣达、GCD
1 、GCD 中兩個(gè)核心概念:『任務(wù)』 和 『隊(duì)列』证薇。
任務(wù):就是執(zhí)行操作的意思度苔,換句話說(shuō)就是你在線程中執(zhí)行的那段代碼。在 GCD 中是放在 block 中的浑度。執(zhí)行任務(wù)有兩種方式:『同步執(zhí)行』 和 『異步執(zhí)行』寇窑。兩者的主要區(qū)別是:是否等待隊(duì)列的任務(wù)執(zhí)行結(jié)束,以及是否具備開(kāi)啟新線程的能力箩张。
- 同步執(zhí)行(sync):
- 同步添加任務(wù)到指定的隊(duì)列中甩骏,在添加的任務(wù)執(zhí)行結(jié)束之前窗市,會(huì)一直等待,直到隊(duì)列里面的任務(wù)完成之后再繼續(xù)執(zhí)行饮笛。
- 只能在當(dāng)前線程中執(zhí)行任務(wù)咨察,不具備開(kāi)啟新線程的能力。
- 異步執(zhí)行(async):
- 異步添加任務(wù)到指定的隊(duì)列中福青,它不會(huì)做任何等待摄狱,可以繼續(xù)執(zhí)行任務(wù)。
- 可以在新的線程中執(zhí)行任務(wù)无午,具備開(kāi)啟新線程的能力媒役。
注意:異步執(zhí)行(async)雖然具有開(kāi)啟新線程的能力,但是并不一定開(kāi)啟新線程指厌。這跟任務(wù)所指定的隊(duì)列類型有關(guān)(下面會(huì)講)刊愚。
隊(duì)列(Dispatch Queue):這里的隊(duì)列指執(zhí)行任務(wù)的等待隊(duì)列,即用來(lái)存放任務(wù)的隊(duì)列踩验。隊(duì)列是一種特殊的線性表鸥诽,采用 FIFO(先進(jìn)先出)的原則,即新任務(wù)總是被插入到隊(duì)列的末尾箕憾,而讀取任務(wù)的時(shí)候總是從隊(duì)列的頭部開(kāi)始讀取牡借。每讀取一個(gè)任務(wù),則從隊(duì)列中釋放一個(gè)任務(wù)袭异。隊(duì)列的結(jié)構(gòu)可參考下圖:
在 GCD 中有兩種隊(duì)列:『串行隊(duì)列』 和 『并發(fā)隊(duì)列』钠龙。兩者都符合 FIFO(先進(jìn)先出)的原則。兩者的主要區(qū)別是:執(zhí)行順序不同御铃,以及開(kāi)啟線程數(shù)不同碴里。
- 串行隊(duì)列(Serial Dispatch Queue):
- 每次只有一個(gè)任務(wù)被執(zhí)行。讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行上真。(只開(kāi)啟一個(gè)線程咬腋,一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))
- 并發(fā)隊(duì)列(Concurrent Dispatch Queue):
- 可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行睡互。(可以開(kāi)啟多個(gè)線程根竿,并且同時(shí)執(zhí)行任務(wù))
注意:并發(fā)隊(duì)列 的并發(fā)功能只有在異步(dispatch_async)方法下才有效。
- 可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行睡互。(可以開(kāi)啟多個(gè)線程根竿,并且同時(shí)執(zhí)行任務(wù))
注意:并發(fā)隊(duì)列 的并發(fā)功能只有在異步(dispatch_async)方法下才有效就珠。
兩者具體區(qū)別如下兩圖所示:
2寇壳、GCD 的使用步驟
兩步:
1.創(chuàng)建一個(gè)隊(duì)列(串行隊(duì)列或并發(fā)隊(duì)列);
2.將任務(wù)追加到任務(wù)的等待隊(duì)列中妻怎,然后系統(tǒng)就會(huì)根據(jù)任務(wù)類型執(zhí)行任務(wù)(同步執(zhí)行或異步執(zhí)
行)壳炎。
2.1 隊(duì)列的創(chuàng)建方法 / 獲取方法
-
可以使用 dispatch_queue_create 方法來(lái)創(chuàng)建隊(duì)列。該方法需要傳入兩個(gè)參數(shù):
- 第一個(gè)參數(shù)表示隊(duì)列的唯一標(biāo)識(shí)符逼侦,用于 DEBUG冕广,可為空疏日。隊(duì)列的名稱推薦使用應(yīng)用程序 ID 這種逆序全程域名。
- 第二個(gè)參數(shù)用來(lái)識(shí)別是串行隊(duì)列還是并發(fā)隊(duì)列撒汉。DISPATCH_QUEUE_SERIAL 表示串行隊(duì)列,DISPATCH_QUEUE_CONCURRENT 表示并發(fā)隊(duì)列涕滋。
-
對(duì)于串行隊(duì)列睬辐,GCD 默認(rèn)提供了:『主隊(duì)列(Main Dispatch Queue)』。
- 所有放在主隊(duì)列中的任務(wù)宾肺,都會(huì)放到主線程中執(zhí)行溯饵。
- 可使用 dispatch_get_main_queue() 方法獲得主隊(duì)列。
-
對(duì)于并發(fā)隊(duì)列锨用,GCD 默認(rèn)提供了 『全局并發(fā)隊(duì)列(Global Dispatch Queue)』丰刊。
- 可以使用 dispatch_get_global_queue 方法來(lái)獲取全局并發(fā)隊(duì)列。需要傳入兩個(gè)參數(shù)增拥。第一個(gè)參數(shù)表示隊(duì)列優(yōu)先級(jí)啄巧,一般用 DISPATCH_QUEUE_PRIORITY_DEFAULT。第二個(gè)參數(shù)暫時(shí)沒(méi)用掌栅,用 0 即可秩仆。
2.2任務(wù)的創(chuàng)建方法
GCD 提供了同步執(zhí)行任務(wù)的創(chuàng)建方法 dispatch_sync 和異步執(zhí)行任務(wù)創(chuàng)建方法
// 同步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_sync(queue, ^{
// 這里放同步執(zhí)行任務(wù)代碼
});
// 異步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_async(queue, ^{
// 這里放異步執(zhí)行任務(wù)代碼
});
六種不同的組合方式
1.同步執(zhí)行 + 并發(fā)隊(duì)列
2.異步執(zhí)行 + 并發(fā)隊(duì)列
3.同步執(zhí)行 + 串行隊(duì)列
4.異步執(zhí)行 + 串行隊(duì)列
5.同步執(zhí)行 + 主隊(duì)列
6.異步執(zhí)行 + 主隊(duì)列
3猾封、 GCD 線程間的通信
在 iOS 開(kāi)發(fā)過(guò)程中澄耍,我們一般在主線程里邊進(jìn)行 UI 刷新,例如:點(diǎn)擊晌缘、滾動(dòng)齐莲、拖拽等事件。我們通常把一些耗時(shí)的操作放在其他線程磷箕,比如說(shuō)圖片下載选酗、文件上傳等耗時(shí)操作。而當(dāng)我們有時(shí)候在其他線程完成了耗時(shí)操作時(shí)搀捷,需要回到主線程星掰,那么就用到了線程之間的通訊。
* 線程間通信
*/
- (void)communication {
// 獲取全局并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 獲取主隊(duì)列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 異步追加任務(wù) 1
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
// 回到主線程
dispatch_async(mainQueue, ^{
// 追加在主線程中執(zhí)行的任務(wù)
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
});
}
4嫩舟、GCD 的其他方法
4.1 GCD 柵欄方法:dispatch_barrier_async
我們有時(shí)需要異步執(zhí)行兩組操作氢烘,而且第一組操作執(zhí)行完之后,才能開(kāi)始執(zhí)行第二組操作家厌。這樣我們就需要一個(gè)相當(dāng)于 柵欄 一樣的一個(gè)方法將兩組異步執(zhí)行的操作組給分割起來(lái)播玖,當(dāng)然這里的操作組里可以包含一個(gè)或多個(gè)任務(wù)。這就需要用到dispatch_barrier_async 方法在兩個(gè)操作組間形成柵欄饭于。
dispatch_barrier_async 方法會(huì)等待前邊追加到并發(fā)隊(duì)列中的任務(wù)全部執(zhí)行完畢之后蜀踏,再將指定的任務(wù)追加到該異步隊(duì)列中维蒙。然后在 dispatch_barrier_async 方法追加的任務(wù)執(zhí)行完畢之后,異步隊(duì)列才恢復(fù)為一般動(dòng)作果覆,接著追加任務(wù)到該異步隊(duì)列并開(kāi)始執(zhí)行
* 柵欄方法 dispatch_barrier_async
*/
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任務(wù) 1
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
dispatch_async(queue, ^{
// 追加任務(wù) 2
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
dispatch_barrier_async(queue, ^{
// 追加任務(wù) barrier
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印當(dāng)前線程
});
dispatch_async(queue, ^{
// 追加任務(wù) 3
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
dispatch_async(queue, ^{
// 追加任務(wù) 4
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
}
4.2 GCD 延時(shí)執(zhí)行方法:dispatch_after
我們經(jīng)常會(huì)遇到這樣的需求:在指定時(shí)間(例如 3 秒)之后執(zhí)行某個(gè)任務(wù)颅痊。可以用 GCD 的dispatch_after 方法來(lái)實(shí)現(xiàn)局待。
需要注意的是:dispatch_after 方法并不是在指定時(shí)間之后才開(kāi)始執(zhí)行處理斑响,而是在指定時(shí)間之后將任務(wù)追加到主隊(duì)列中。嚴(yán)格來(lái)說(shuō)钳榨,這個(gè)時(shí)間并不是絕對(duì)準(zhǔn)確的舰罚,但想要大致延遲執(zhí)行任務(wù),dispatch_after 方法是很有效的薛耻。
* 延時(shí)執(zhí)行方法 dispatch_after
*/
- (void)after {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"asyncMain---begin");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0 秒后異步追加任務(wù)代碼到主隊(duì)列营罢,并開(kāi)始執(zhí)行
NSLog(@"after---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
}
4.3 GCD 一次性代碼(只執(zhí)行一次):dispatch_once
我們?cè)趧?chuàng)建單例、或者有整個(gè)程序運(yùn)行過(guò)程中只執(zhí)行一次的代碼時(shí)饼齿,我們就用到了 GCD 的 dispatch_once 方法饲漾。使用 dispatch_once 方法能保證某段代碼在程序運(yùn)行過(guò)程中只被執(zhí)行 1 次,并且即使在多線程的環(huán)境下候醒,dispatch_once 也可以保證線程安全能颁。
* 一次性代碼(只執(zhí)行一次)dispatch_once
*/
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行 1 次的代碼(這里面默認(rèn)是線程安全的)
});
}
4.4 GCD 快速迭代方法:dispatch_apply
通常我們會(huì)用 for 循環(huán)遍歷,但是 GCD 給我們提供了快速迭代的方法 dispatch_apply倒淫。dispatch_apply 按照指定的次數(shù)將指定的任務(wù)追加到指定的隊(duì)列中伙菊,并等待全部隊(duì)列執(zhí)行結(jié)束。
如果是在串行隊(duì)列中使用 dispatch_apply敌土,那么就和 for 循環(huán)一樣镜硕,按順序同步執(zhí)行。但是這樣就體現(xiàn)不出快速迭代的意義了返干。
我們可以利用并發(fā)隊(duì)列進(jìn)行異步執(zhí)行兴枯。比如說(shuō)遍歷 0~5 這 6 個(gè)數(shù)字,for 循環(huán)的做法是每次取出一個(gè)元素矩欠,逐個(gè)遍歷财剖。dispatch_apply 可以 在多個(gè)線程中同時(shí)(異步)遍歷多個(gè)數(shù)字。
還有一點(diǎn)癌淮,無(wú)論是在串行隊(duì)列躺坟,還是并發(fā)隊(duì)列中,dispatch_apply 都會(huì)等待全部任務(wù)執(zhí)行完畢乳蓄,這點(diǎn)就像是同步操作咪橙,也像是隊(duì)列組中的 dispatch_group_wait方法。
* 快速迭代方法 dispatch_apply
*/
- (void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
}
4.5 GCD 隊(duì)列組:dispatch_group
有時(shí)候我們會(huì)有這樣的需求:分別異步執(zhí)行2個(gè)耗時(shí)任務(wù),然后當(dāng)2個(gè)耗時(shí)任務(wù)都執(zhí)行完畢后再回到主線程執(zhí)行任務(wù)美侦。這時(shí)候我們可以用到 GCD 的隊(duì)列組产舞。
調(diào)用隊(duì)列組的 dispatch_group_async 先把任務(wù)放到隊(duì)列中,然后將隊(duì)列放入隊(duì)列組中菠剩∫酌ǎ或者使用隊(duì)列組的 dispatch_group_enter、dispatch_group_leave 組合來(lái)實(shí)現(xiàn) dispatch_group_async赠叼。
調(diào)用隊(duì)列組的 dispatch_group_notify 回到指定線程執(zhí)行任務(wù)擦囊。或者使用 dispatch_group_wait 回到當(dāng)前線程繼續(xù)向下執(zhí)行(會(huì)阻塞當(dāng)前線程)嘴办。
4.5.1 dispatch_group_notify
監(jiān)聽(tīng) group 中任務(wù)的完成狀態(tài),當(dāng)所有的任務(wù)都執(zhí)行完成后买鸽,追加任務(wù)到 group 中涧郊,并執(zhí)行任務(wù)。
當(dāng)所有任務(wù)都執(zhí)行完成之后眼五,才執(zhí)行 dispatch_group_notify 相關(guān) block 中的任務(wù)妆艘。4.5.2 dispatch_group_wait
暫停當(dāng)前線程(阻塞當(dāng)前線程),等待指定的 group 中的任務(wù)執(zhí)行完成后看幼,才會(huì)往下繼續(xù)執(zhí)行批旺。
當(dāng)所有任務(wù)執(zhí)行完成之后,才執(zhí)行 dispatch_group_wait 之后的操作诵姜。但是汽煮,使用dispatch_group_wait 會(huì)阻塞當(dāng)前線程。4.5.3 dispatch_group_enter棚唆、dispatch_group_leave
dispatch_group_enter 標(biāo)志著一個(gè)任務(wù)追加到 group暇赤,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù) +1
dispatch_group_leave 標(biāo)志著一個(gè)任務(wù)離開(kāi)了 group宵凌,執(zhí)行一次鞋囊,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù) -1。
當(dāng) group 中未執(zhí)行完畢任務(wù)數(shù)為0的時(shí)候瞎惫,才會(huì)使 dispatch_group_wait 解除阻塞溜腐,以及執(zhí)行追加到 dispatch_group_notify 中的任務(wù)。
當(dāng)所有任務(wù)執(zhí)行完成之后瓜喇,才執(zhí)行 dispatch_group_notify 中的任務(wù)挺益。這里的dispatch_group_enter、dispatch_group_leave 組合欠橘,其實(shí)等同于dispatch_group_async矩肩。
4.6 GCD 信號(hào)量:dispatch_semaphore
GCD 中的信號(hào)量是指 Dispatch Semaphore,是持有計(jì)數(shù)的信號(hào)。類似于過(guò)高速路收費(fèi)站的欄桿黍檩〔媾郏可以通過(guò)時(shí),打開(kāi)欄桿刽酱,不可以通過(guò)時(shí)喳逛,關(guān)閉欄桿。在 Dispatch Semaphore 中棵里,使用計(jì)數(shù)來(lái)完成這個(gè)功能润文,計(jì)數(shù)小于 0 時(shí)等待,不可通過(guò)殿怜。計(jì)數(shù)為 0 或大于 0 時(shí)典蝌,計(jì)數(shù)減 1 且不等待,可通過(guò)头谜。
Dispatch Semaphore 提供了三個(gè)方法:
- dispatch_semaphore_create:創(chuàng)建一個(gè) Semaphore 并初始化信號(hào)的總量
- dispatch_semaphore_signal:發(fā)送一個(gè)信號(hào)骏掀,讓信號(hào)總量加 1
- dispatch_semaphore_wait:可以使總信號(hào)量減 1,信號(hào)總量小于 0 時(shí)就會(huì)一直等待(阻塞所在線程)柱告,否則就可以正常執(zhí)行截驮。
Dispatch Semaphore 在實(shí)際開(kāi)發(fā)中主要用于:
保持線程同步,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)
保證線程安全际度,為線程加鎖
- 4.6.1 Dispatch Semaphore 線程同步
我們?cè)陂_(kāi)發(fā)中葵袭,會(huì)遇到這樣的需求:異步執(zhí)行耗時(shí)任務(wù),并使用異步執(zhí)行的結(jié)果進(jìn)行一些額外的操作乖菱。換句話說(shuō)坡锡,相當(dāng)于,將將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)块请。比如說(shuō):AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法娜氏。通過(guò)引入信號(hào)量的方式,等待異步執(zhí)行任務(wù)結(jié)果墩新,獲取到 tasks贸弥,然后再返回該 tasks。 - 4.6.2 Dispatch Semaphore 線程安全和線程同步(為線程加鎖)
線程安全:如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行海渊,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼绵疲。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的臣疑,就是線程安全的盔憨。
若每個(gè)線程中對(duì)全局變量、靜態(tài)變量只有讀操作讯沈,而無(wú)寫(xiě)操作郁岩,一般來(lái)說(shuō),這個(gè)全局變量是線程安全的;若有多個(gè)線程同時(shí)執(zhí)行寫(xiě)操作(更改變量)问慎,一般都需要考慮線程同步萍摊,否則的話就可能影響線程安全。
線程同步:可理解為線程 A 和 線程 B 一塊配合如叼,A 執(zhí)行到一定程度時(shí)要依靠線程 B 的某個(gè)結(jié)果冰木,于是停下來(lái),示意 B 運(yùn)行笼恰;B 依言執(zhí)行踊沸,再將結(jié)果給 A;A 再繼續(xù)操作社证。
二逼龟、NSThred
1.1 創(chuàng)建、啟動(dòng)線程
- 先創(chuàng)建線程追葡,再啟動(dòng)線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// 2. 啟動(dòng)線程
[thread start]; // 線程一啟動(dòng)审轮,就會(huì)在線程thread中執(zhí)行self的run方法
// 新線程調(diào)用方法,里邊為需要執(zhí)行的任務(wù)
- (void)run {
NSLog(@"%@", [NSThread currentThread]);
}
- 創(chuàng)建線程后自動(dòng)啟動(dòng)線程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
// 新線程調(diào)用方法辽俗,里邊為需要執(zhí)行的任務(wù)
- (void)run {
NSLog(@"%@", [NSThread currentThread]);
}
- 隱式創(chuàng)建并啟動(dòng)線程
[self performSelectorInBackground:@selector(run) withObject:nil];
// 新線程調(diào)用方法,里邊為需要執(zhí)行的任務(wù)
- (void)run {
NSLog(@"%@", [NSThread currentThread]);
}
1.2 線程相關(guān)用法
+ (NSThread *)mainThread;
// 判斷是否為主線程(對(duì)象方法)
- (BOOL)isMainThread;
// 判斷是否為主線程(類方法)
+ (BOOL)isMainThread;
// 獲得當(dāng)前線程
NSThread *current = [NSThread currentThread];
// 線程的名字——setter方法
- (void)setName:(NSString *)n;
// 線程的名字——getter方法
- (NSString *)name;
1.3 線程狀態(tài)控制方法
- 啟動(dòng)線程方法
- (void)start;
// 線程進(jìn)入就緒狀態(tài) -> 運(yùn)行狀態(tài)篡诽。當(dāng)線程任務(wù)執(zhí)行完畢崖飘,自動(dòng)進(jìn)入死亡狀態(tài)
- 阻塞(暫停)線程方法
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 線程進(jìn)入阻塞狀態(tài)
- 強(qiáng)制停止線程
+ (void)exit;
// 線程進(jìn)入死亡狀態(tài)
1.4 線程之間的通信
在開(kāi)發(fā)中,我們經(jīng)常會(huì)在子線程進(jìn)行耗時(shí)操作杈女,操作結(jié)束后再回到主線程去刷新 UI朱浴。這就涉及到了子線程和主線程之間的通信。我們先來(lái)了解一下官方關(guān)于 NSThread 的線程間通信的方法达椰。
// 在主線程上執(zhí)行操作
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray<NSString *> *)array;
// equivalent to the first method with kCFRunLoopCommonModes
// 在指定線程上執(zhí)行操作
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
// 在當(dāng)前線程上執(zhí)行操作翰蠢,調(diào)用 NSObject 的 performSelector:相關(guān)方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
1.5 NSThread 線程安全和線程同步
線程安全:如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼啰劲。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的梁沧,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的蝇裤。
若每個(gè)線程中對(duì)全局變量廷支、靜態(tài)變量只有讀操作,而無(wú)寫(xiě)操作栓辜,一般來(lái)說(shuō)恋拍,這個(gè)全局變量是線程安全的;若有多個(gè)線程同時(shí)執(zhí)行寫(xiě)操作(更改變量)藕甩,一般都需要考慮線程同步施敢,否則的話就可能影響線程安全。
線程同步:可理解為線程 A 和 線程 B 一塊配合,A 執(zhí)行到一定程度時(shí)要依靠線程 B 的某個(gè)結(jié)果僵娃,于是停下來(lái)概作,示意 B 運(yùn)行;B 依言執(zhí)行悯许,再將結(jié)果給 A仆嗦;A 再繼續(xù)操作。
1.6 線程的狀態(tài)轉(zhuǎn)換
當(dāng)我們新建一條線程 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
先壕,在內(nèi)存中的表現(xiàn)為:
當(dāng)調(diào)用[thread start];
后瘩扼,系統(tǒng)把線程對(duì)象放入可調(diào)度線程池中,線程對(duì)象進(jìn)入就緒狀態(tài)垃僚,如下圖所示集绰。
下邊我們來(lái)看看當(dāng)前線程的狀態(tài)轉(zhuǎn)換。
- 如果CPU現(xiàn)在調(diào)度當(dāng)前線程對(duì)象谆棺,則當(dāng)前線程對(duì)象進(jìn)入運(yùn)行狀態(tài)栽燕,如果CPU調(diào)度其他線程對(duì)象,則當(dāng)前線程對(duì)象回到就緒狀態(tài)改淑。
- 如果CPU在運(yùn)行當(dāng)前線程對(duì)象的時(shí)候調(diào)用了sleep方法\等待同步鎖碍岔,則當(dāng)前線程對(duì)象就進(jìn)入了阻塞狀態(tài),等到sleep到時(shí)\得到同步鎖朵夏,則回到就緒狀態(tài)蔼啦。
- 如果CPU在運(yùn)行當(dāng)前線程對(duì)象的時(shí)候線程任務(wù)執(zhí)行完畢\異常強(qiáng)制退出,則當(dāng)前線程對(duì)象進(jìn)入死亡狀態(tài)仰猖。
具體當(dāng)前線程對(duì)象的狀態(tài)變化如下圖所示
三捏肢、NSOperation
1. NSOperation、NSOperationQueue 簡(jiǎn)介
NSOperation饥侵、NSOperationQueue 是蘋(píng)果提供給我們的一套多線程解決方案鸵赫。實(shí)際上 NSOperation、NSOperationQueue 是基于 GCD 更高一層的封裝躏升,完全面向?qū)ο蟊绨簟5潜?GCD 更簡(jiǎn)單易用、代碼可讀性也更高煮甥。
為什么要使用 NSOperation盗温、NSOperationQueue?
1.可添加完成的代碼塊成肘,在操作完成后執(zhí)行卖局。
2.添加操作之間的依賴關(guān)系,方便的控制執(zhí)行順序双霍。
3.設(shè)定操作執(zhí)行的優(yōu)先級(jí)砚偶。
4.可以很方便的取消一個(gè)操作的執(zhí)行批销。
5.使用 KVO 觀察對(duì)操作執(zhí)行狀態(tài)的更改:isExecuteing、isFinished染坯、isCancelled均芽。
2. NSOperation、NSOperationQueue 操作和操作隊(duì)列
既然是基于 GCD 的更高一層的封裝单鹿。那么掀宋,GCD 中的一些概念同樣適用于 NSOperation、NSOperationQueue仲锄。在 NSOperation劲妙、NSOperationQueue 中也有類似的任務(wù)(操作)和隊(duì)列(操作隊(duì)列)的概念。
-
操作(Operation):
- 執(zhí)行操作的意思儒喊,換句話說(shuō)就是你在線程中執(zhí)行的那段代碼镣奋。
- 在 GCD 中是放在 block 中的。在 NSOperation 中怀愧,我們使用 NSOperation 子類 NSInvocationOperation侨颈、NSBlockOperation,或者自定義子類來(lái)封裝操作芯义。
-
操作隊(duì)列(Operation Queues):
- 這里的隊(duì)列指操作隊(duì)列哈垢,即用來(lái)存放操作的隊(duì)列。不同于 GCD 中的調(diào)度隊(duì)列 FIFO(先進(jìn)先出)的原則扛拨。NSOperationQueue 對(duì)于添加到隊(duì)列中的操作温赔,首先進(jìn)入準(zhǔn)備就緒的狀態(tài)(就緒狀態(tài)取決于操作之間的依賴關(guān)系),然后進(jìn)入就緒狀態(tài)的操作的開(kāi)始執(zhí)行順序(非結(jié)束執(zhí)行順序)由操作之間相對(duì)的優(yōu)先級(jí)決定(優(yōu)先級(jí)是操作對(duì)象自身的屬性)鬼癣。
- 操作隊(duì)列通過(guò)設(shè)置最大并發(fā)操作數(shù)(maxConcurrentOperationCount)來(lái)控制并發(fā)、串行啤贩。
- NSOperationQueue 為我們提供了兩種不同類型的隊(duì)列:主隊(duì)列和自定義隊(duì)列待秃。主隊(duì)列運(yùn)行在主線程之上,而自定義隊(duì)列在后臺(tái)執(zhí)行痹屹。
3. NSOperation章郁、NSOperationQueue 使用步驟
NSOperation 需要配合 NSOperationQueue 來(lái)實(shí)現(xiàn)多線程。因?yàn)槟J(rèn)情況下志衍,NSOperation 單獨(dú)使用時(shí)系統(tǒng)同步執(zhí)行操作暖庄,配合 NSOperationQueue 我們能更好的實(shí)現(xiàn)異步執(zhí)行。
NSOperation 實(shí)現(xiàn)多線程的使用步驟分為三步:
1.創(chuàng)建操作:先將需要執(zhí)行的操作封裝到一個(gè) NSOperation 對(duì)象中楼肪。
2.創(chuàng)建隊(duì)列:創(chuàng)建 NSOperationQueue 對(duì)象培廓。
3.將操作加入到隊(duì)列中:將 NSOperation 對(duì)象添加到 NSOperationQueue 對(duì)象中。
之后呢春叫,系統(tǒng)就會(huì)自動(dòng)將 NSOperationQueue 中的 NSOperation 取出來(lái)肩钠,在新線程中執(zhí)行操作泣港。
4. NSOperation 和 NSOperationQueue 基本使用
4.1 創(chuàng)建操作
NSOperation 是個(gè)抽象類,不能用來(lái)封裝操作价匠。我們只有使用它的子類來(lái)封裝操作当纱。我們有三種方式來(lái)封裝操作。
1.使用子類 NSInvocationOperation
2.使用子類 NSBlockOperation
3.自定義繼承自 NSOperation 的子類踩窖,通過(guò)實(shí)現(xiàn)內(nèi)部相應(yīng)的方法來(lái)封裝操作坡氯。
在不使用 NSOperationQueue,單獨(dú)使用 NSOperation 的情況下系統(tǒng)同步執(zhí)行操作洋腮,下面我們學(xué)習(xí)以下操作的三種創(chuàng)建方式箫柳。
4.1.1 使用子類 NSInvocationOperation
* 使用子類 NSInvocationOperation
*/
- (void)useInvocationOperation {
// 1.創(chuàng)建 NSInvocationOperation 對(duì)象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
// 2.調(diào)用 start 方法開(kāi)始執(zhí)行操作
[op start];
}
/**
* 任務(wù)1
*/
- (void)task1 {
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}
在沒(méi)有使用 NSOperationQueue、在主線程中單獨(dú)使用使用子類 NSInvocationOperation 執(zhí)行一個(gè)操作的情況下徐矩,操作是在當(dāng)前線程執(zhí)行的滞时,并沒(méi)有開(kāi)啟新線程。
如果在其他線程中執(zhí)行操作滤灯,則打印結(jié)果為其他線程坪稽。
在其他線程中單獨(dú)使用子類 NSInvocationOperation,操作是在當(dāng)前調(diào)用的其他線程執(zhí)行的鳞骤,并沒(méi)有開(kāi)啟新線程窒百。
4.1.2 使用子類 NSBlockOperation
/**
* 使用子類 NSBlockOperation
*/
- (void)useBlockOperation {
// 1.創(chuàng)建 NSBlockOperation 對(duì)象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
// 2.調(diào)用 start 方法開(kāi)始執(zhí)行操作
[op start];
}
- 在沒(méi)有使用 NSOperationQueue、在主線程中單獨(dú)使用 NSBlockOperation 執(zhí)行一個(gè)操作的情況下豫尽,操作是在當(dāng)前線程執(zhí)行的篙梢,并沒(méi)有開(kāi)啟新線程。
注意:和上邊 NSInvocationOperation 使用一樣美旧。因?yàn)榇a是在主線程中調(diào)用的渤滞,所以打印結(jié)果為主線程。如果在其他線程中執(zhí)行操作榴嗅,則打印結(jié)果為其他線程妄呕。
但是,NSBlockOperation
還提供了一個(gè)方法 addExecutionBlock:
嗽测,通過(guò) addExecutionBlock:
就可以為 NSBlockOperation
添加額外的操作绪励。這些操作(包括 blockOperationWithBlock
中的操作)可以在不同的線程中同時(shí)(并發(fā))執(zhí)行。只有當(dāng)所有相關(guān)的操作已經(jīng)完成執(zhí)行時(shí)唠粥,才視為完成疏魏。
如果添加的操作多的話,blockOperationWithBlock:
中的操作也可能會(huì)在其他線程(非當(dāng)前線程)中執(zhí)行晤愧,這是由系統(tǒng)決定的大莫,并不是說(shuō)添加到 blockOperationWithBlock:
中的操作一定會(huì)在當(dāng)前線程中執(zhí)行。(可以使用 addExecutionBlock:
多添加幾個(gè)操作試試)官份。
/**
* 使用子類 NSBlockOperation
* 調(diào)用方法 AddExecutionBlock:
*/
- (void)useBlockOperationAddExecutionBlock {
// 1.創(chuàng)建 NSBlockOperation 對(duì)象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
// 2.添加額外的操作
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"5---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"6---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"7---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"8---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
// 3.調(diào)用 start 方法開(kāi)始執(zhí)行操作
[op start];
}
- 可以看出:使用子類
NSBlockOperation
葵硕,并調(diào)用方法AddExecutionBlock:
的情況下眉抬,blockOperationWithBlock:
方法中的操作 和addExecutionBlock:
中的操作是在不同的線程中并發(fā)執(zhí)行的。而且懈凹,這次執(zhí)行結(jié)果中blockOperationWithBlock:
方法中的操作也不是在當(dāng)前線程(主線程)中執(zhí)行的蜀变。從而印證了blockOperationWithBlock:
中的操作也可能會(huì)在其他線程(非當(dāng)前線程)中執(zhí)行。
一般情況下介评,如果一個(gè) NSBlockOperation
對(duì)象封裝了多個(gè)操作库北。NSBlockOperation
是否開(kāi)啟新線程,取決于操作的個(gè)數(shù)们陆。如果添加的操作的個(gè)數(shù)多寒瓦,就會(huì)自動(dòng)開(kāi)啟新線程。當(dāng)然開(kāi)啟的線程數(shù)是由系統(tǒng)來(lái)決定的坪仇。
4.1.3 使用自定義繼承自 NSOperation
的子類
如果使用子類 NSInvocationOperation杂腰、NSBlockOperation
不能滿足日常需求,我們可以使用自定義繼承自 NSOperation
的子類椅文∥购埽可以通過(guò)重寫(xiě) main
或者 start
方法 來(lái)定義自己的 NSOperation
對(duì)象。重寫(xiě)main
方法比較簡(jiǎn)單皆刺,我們不需要管理操作的狀態(tài)屬性 isExecuting
和 isFinished
少辣。當(dāng) main
執(zhí)行完返回的時(shí)候,這個(gè)操作就結(jié)束了羡蛾。
先定義一個(gè)繼承自 NSOperation
的子類漓帅,重寫(xiě)main
方法
// YSCOperation.h 文件
#import <Foundation/Foundation.h>
@interface YSCOperation : NSOperation
@end
// YSCOperation.m 文件
#import "YSCOperation.h"
@implementation YSCOperation
- (void)main {
if (!self.isCancelled) {
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"1---%@", [NSThread currentThread]);
}
}
}
@end
然后使用的時(shí)候?qū)腩^文件YSCOperation.h
。
/**
* 使用自定義繼承自 NSOperation 的子類
*/
- (void)useCustomOperation {
// 1.創(chuàng)建 YSCOperation 對(duì)象
YSCOperation *op = [[YSCOperation alloc] init];
// 2.調(diào)用 start 方法開(kāi)始執(zhí)行操作
[op start];
}
- 在沒(méi)有使用
NSOperationQueue
痴怨、在主線程單獨(dú)使用自定義繼承自NSOperation
的子類的情況下忙干,是在主線程執(zhí)行操作,并沒(méi)有開(kāi)啟新線程浪藻。
4.2 創(chuàng)建隊(duì)列
4.3 將操作加入到隊(duì)列中
5. NSOperationQueue 控制串行執(zhí)行豪直、并發(fā)執(zhí)行
6. NSOperation 操作依賴
NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之間的依賴關(guān)系珠移。通過(guò)操作依賴,我們可以很方便的控制操作之間的執(zhí)行先后順序末融。NSOperation 提供了3個(gè)接口供我們管理和查看依賴叮贩。
-
- (void)addDependency:(NSOperation *)op;
添加依賴谨湘,使當(dāng)前操作依賴于操作 op 的完成。 -
- (void)removeDependency:(NSOperation *)op;
移除依賴,取消當(dāng)前操作對(duì)操作 op 的依賴访诱。 -
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
在當(dāng)前操作開(kāi)始執(zhí)行之前完成執(zhí)行的所有操作對(duì)象數(shù)組。
7. NSOperation 優(yōu)先級(jí)
NSOperation 提供了queuePriority
(優(yōu)先級(jí))屬性贪染,queuePriority
屬性適用于同一操作隊(duì)列中的操作,不適用于不同操作隊(duì)列中的操作涂乌。默認(rèn)情況下,所有新創(chuàng)建的操作對(duì)象優(yōu)先級(jí)都是NSOperationQueuePriorityNormal
英岭。但是我們可以通過(guò)setQueuePriority:
方法來(lái)改變當(dāng)前操作在同一隊(duì)列中的執(zhí)行優(yōu)先級(jí)湾盒。
// 優(yōu)先級(jí)的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
對(duì)于添加到隊(duì)列中的操作,首先進(jìn)入準(zhǔn)備就緒的狀態(tài)(就緒狀態(tài)取決于操作之間的依賴關(guān)系)诅妹,然后進(jìn)入就緒狀態(tài)的操作的開(kāi)始執(zhí)行順序(非結(jié)束執(zhí)行順序)由操作之間相對(duì)的優(yōu)先級(jí)決定(優(yōu)先級(jí)是操作對(duì)象自身的屬性)罚勾。
那么,什么樣的操作才是進(jìn)入就緒狀態(tài)的操作呢吭狡?
-
當(dāng)一個(gè)操作的所有依賴都已經(jīng)完成時(shí)尖殃,操作對(duì)象通常會(huì)進(jìn)入準(zhǔn)備就緒狀態(tài),等待執(zhí)行划煮。
舉個(gè)例子送丰,現(xiàn)在有4個(gè)優(yōu)先級(jí)都是NSOperationQueuePriorityNormal
(默認(rèn)級(jí)別)的操作:op1,op2弛秋,op3器躏,op4
。其中op3
依賴于op2铐懊,op2
依賴于op1
邀桑,即op3 -> op2 -> op1
。現(xiàn)在將這4個(gè)操作添加到隊(duì)列中并發(fā)執(zhí)行科乎。- 因?yàn)?code>op1 和
op4
都沒(méi)有需要依賴的操作壁畸,所以在op1,op4
執(zhí)行之前茅茂,就是處于準(zhǔn)備就緒狀態(tài)的操作捏萍。 - 而
op3
和op2
都有依賴的操作(op3
依賴于op2
,op2
依賴于op1
)空闲,所以op3
和op2
都不是準(zhǔn)備就緒狀態(tài)下的操作令杈。
- 因?yàn)?code>op1 和
理解了進(jìn)入就緒狀態(tài)的操作,那么我們就理解了queuePriority
屬性的作用對(duì)象碴倾。
queuePriority 屬性決定了進(jìn)入準(zhǔn)備就緒狀態(tài)下的操作之間的開(kāi)始執(zhí)行順序逗噩。并且,優(yōu)先級(jí)不能取代依賴關(guān)系跌榔。
- 如果一個(gè)隊(duì)列中既包含高優(yōu)先級(jí)操作异雁,又包含低優(yōu)先級(jí)操作,并且兩個(gè)操作都已經(jīng)準(zhǔn)備就緒僧须,那么隊(duì)列先執(zhí)行高優(yōu)先級(jí)操作纲刀。比如上例中,如果
op1
和op4
是不同優(yōu)先級(jí)的操作担平,那么就會(huì)先執(zhí)行優(yōu)先級(jí)高的操作示绊。 - 如果锭部,一個(gè)隊(duì)列中既包含了準(zhǔn)備就緒狀態(tài)的操作,又包含了未準(zhǔn)備就緒的操作面褐,未準(zhǔn)備就緒的操作優(yōu)先級(jí)比準(zhǔn)備就緒的操作優(yōu)先級(jí)高拌禾。那么,雖然準(zhǔn)備就緒的操作優(yōu)先級(jí)低盆耽,也會(huì)優(yōu)先執(zhí)行蹋砚。優(yōu)先級(jí)不能取代依賴關(guān)系。如果要控制操作間的啟動(dòng)順序摄杂,則必須使用依賴關(guān)系坝咐。
8. NSOperation、NSOperationQueue 線程間的通信
在 iOS 開(kāi)發(fā)過(guò)程中析恢,我們一般在主線程里邊進(jìn)行 UI 刷新墨坚,例如:點(diǎn)擊、滾動(dòng)映挂、拖拽等事件泽篮。我們通常把一些耗時(shí)的操作放在其他線程,比如說(shuō)圖片下載柑船、文件上傳等耗時(shí)操作帽撑。而當(dāng)我們有時(shí)候在其他線程完成了耗時(shí)操作時(shí),需要回到主線程鞍时,那么就用到了線程之間的通訊亏拉。
/**
* 線程間通信
*/
- (void)communication {
// 1.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 2.添加操作
[queue addOperationWithBlock:^{
// 異步進(jìn)行耗時(shí)操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
// 回到主線程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 進(jìn)行一些 UI 刷新等操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
}];
}
9. NSOperation、NSOperationQueue 線程同步和線程安全
線程安全解決方案:可以給線程加鎖逆巍,在一個(gè)線程執(zhí)行該操作的時(shí)候及塘,不允許其他線程進(jìn)行操作。iOS 實(shí)現(xiàn)線程加鎖有很多種方式锐极。@synchronized笙僚、 NSLock、NSRecursiveLock灵再、NSCondition肋层、NSConditionLock、pthread_mutex翎迁、dispatch_semaphore栋猖、OSSpinLock、atomic(property) set/ge等等各種方式鸳兽。這里我們使用 NSLock 對(duì)象來(lái)解決線程同步問(wèn)題。NSLock 對(duì)象可以通過(guò)進(jìn)入鎖時(shí)調(diào)用 lock 方法罕拂,解鎖時(shí)調(diào)用 unlock 方法來(lái)保證線程安全揍异。
10. NSOperation全陨、NSOperationQueue 常用屬性和方法歸納
10.1 NSOperation 常用屬性和方法
1.取消操作方法
-
- (void)cancel;
可取消操作,實(shí)質(zhì)是標(biāo)記 isCancelled 狀態(tài)衷掷。
2.判斷操作狀態(tài)方法 -
- (BOOL)isFinished;
判斷操作是否已經(jīng)結(jié)束辱姨。 -
- (BOOL)isCancelled;
判斷操作是否已經(jīng)標(biāo)記為取消。 -
- (BOOL)isExecuting;
判斷操作是否正在在運(yùn)行戚嗅。 -
- (BOOL)isReady;
判斷操作是否處于準(zhǔn)備就緒狀態(tài)雨涛,這個(gè)值和操作的依賴關(guān)系相關(guān)。
3.操作同步 -
- (void)waitUntilFinished;
阻塞當(dāng)前線程懦胞,直到該操作結(jié)束替久。可用于線程執(zhí)行順序的同步躏尉。 -
- (void)setCompletionBlock:(void (^)(void))block;
completionBlock 會(huì)在當(dāng)前操作執(zhí)行完畢時(shí)執(zhí)行 completionBlock蚯根。 -
- (void)addDependency:(NSOperation *)op;
添加依賴,使當(dāng)前操作依賴于操作 op 的完成胀糜。 -
- (void)removeDependency:(NSOperation *)op;
移除依賴颅拦,取消當(dāng)前操作對(duì)操作 op 的依賴。 -
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
在當(dāng)前操作開(kāi)始執(zhí)行之前完成執(zhí)行的所有操作對(duì)象數(shù)組教藻。
10.2 NSOperationQueue 常用屬性和方法
1.取消/暫停/恢復(fù)操作
-
- (void)cancelAllOperations;
可以取消隊(duì)列的所有操作距帅。 -
- (BOOL)isSuspended;
判斷隊(duì)列是否處于暫停狀態(tài)。 YES 為暫停狀態(tài)括堤,NO 為恢復(fù)狀態(tài)碌秸。 -
- (void)setSuspended:(BOOL)b;
可設(shè)置操作的暫停和恢復(fù),YES 代表暫停隊(duì)列痊臭,NO 代表恢復(fù)隊(duì)列哮肚。
2.操作同步 -
- (void)waitUntilAllOperationsAreFinished;
阻塞當(dāng)前線程,直到隊(duì)列中的操作全部執(zhí)行完畢广匙。
3.添加/獲取操作 -
- (void)addOperationWithBlock:(void (^)(void))block;
向隊(duì)列中添加一個(gè) NSBlockOperation 類型操作對(duì)象允趟。 -
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;
向隊(duì)列中添加操作數(shù)組,wait 標(biāo)志是否阻塞當(dāng)前線程直到所有操作結(jié)束 -
- (NSArray *)operations;
當(dāng)前在隊(duì)列中的操作數(shù)組(某個(gè)操作執(zhí)行結(jié)束后會(huì)自動(dòng)從這個(gè)數(shù)組清除)鸦致。 -
- (NSUInteger)operationCount;
當(dāng)前隊(duì)列中的操作數(shù)潮剪。
4.獲取隊(duì)列 -
+ (id)currentQueue;
獲取當(dāng)前隊(duì)列,如果當(dāng)前線程不是在 NSOperationQueue 上運(yùn)行則返回 nil分唾。 -
+ (id)mainQueue;
獲取主隊(duì)列抗碰。
注意:
1.這里的暫停和取消(包括操作的取消和隊(duì)列的取消)并不代表可以將當(dāng)前的操作立即取消,而是當(dāng)當(dāng)前的操作執(zhí)行完畢之后不再執(zhí)行新的操作绽乔。
2.暫停和取消的區(qū)別就在于:暫停操作之后還可以恢復(fù)操作弧蝇,繼續(xù)向下執(zhí)行;而取消操作之后,所有的操作就清空了看疗,無(wú)法再接著執(zhí)行剩下的操作沙峻。