一.概述與實(shí)現(xiàn)方案
1. 線程與進(jìn)程
多線程在iOS中有著舉足輕重的地位阱冶,是每一位開發(fā)者都必備的技能舔示,當(dāng)然也是面試潮胫茫考的技術(shù)點(diǎn)撑螺,本文主要是探究我們實(shí)際開發(fā)或者面試中遇到的多線程問題。比如什么是線程城瞎?它跟進(jìn)程是什么關(guān)系渤闷,隊(duì)列跟線程什么關(guān)系,同步脖镀、異步飒箭、并發(fā)(并行)、串行
這些概念又怎么來理解,iOS有哪些常用多線程方案补憾,以及線程同步技術(shù)有哪些等等漫萄。
線程(英語:thread)是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。大部分情況下盈匾,它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位毕骡。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流削饵,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)未巫。 --- 維基百科
這里又多了一個(gè) 進(jìn)程
,那什么是進(jìn)程呢,說白了就是是指在操作系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序窿撬,如微信、支付寶app等都是一個(gè)進(jìn)程叙凡。線程是就是進(jìn)程的基本執(zhí)行單元劈伴,一個(gè)進(jìn)程的所有任務(wù)都在線程中執(zhí)行。也就是說 一個(gè)進(jìn)程最少要有一個(gè)線程握爷,這個(gè)線程就是主線程跛璧。當(dāng)然在我們實(shí)際使用過程中不可能只有一條主線程,我們?yōu)樘岣叱绦虻膱?zhí)行效率新啼,往往需要開辟多條子線程去執(zhí)行一些耗時(shí)任務(wù)追城,這里就引出了多線程的概念。
多線程(英語:multithreading)燥撞,是指從軟件或者硬件上實(shí)現(xiàn)多個(gè)線程并發(fā)執(zhí)行的技術(shù)
根據(jù)操作系統(tǒng)與硬件的不同分為兩類:軟件多線程
與硬件多線程
軟件多線程: 即便CPU只能運(yùn)行一個(gè)線程座柱,操作系統(tǒng)也可以通過快速的在不同線程之間進(jìn)行切換,由于時(shí)間間隔很小物舒,來給用戶造成一種多個(gè)線程同時(shí)運(yùn)行的假象
硬件多線程: 如果CPU有多個(gè)核心色洞,操作系統(tǒng)可以讓每個(gè)核心執(zhí)行一條線程,從而具有真正的同時(shí)執(zhí)行多個(gè)線程的能力冠胯,當(dāng)然由于任務(wù)數(shù)量遠(yuǎn)遠(yuǎn)多于CPU的核心數(shù)量火诸,所以,操作系統(tǒng)也會(huì)自動(dòng)把很多任務(wù)輪流調(diào)度到每個(gè)核心上執(zhí)行涵叮。
以上都是google出來的一大堆東西惭蹂,比較抽象,沒關(guān)系我們來看下我們實(shí)際iOS開發(fā)中用到的多線程技術(shù)割粮。
2.iOS中的多線程方案
iOS 中的多線程方案主要有四種 PThread
盾碗、NSThread
、GCD
舀瓢、NSOperation
廷雅,PThread
是一套純粹C
語言的API,能適用于Unix\Linux\Windows等系統(tǒng),線程生命周期需要程序員自己管理,使用難度較大航缀,在我們的實(shí)際開發(fā)中幾乎用不到商架,在這里我們不做過多介紹,感興趣的直接去百度芥玉。我們著重介紹另外三種方案蛇摸。
這里解釋一下線程的生命周期,所謂的線程的生命周期就是線程從創(chuàng)建到死亡的過程灿巧。一般會(huì)經(jīng)歷:
新建 - 就緒 - 運(yùn)行 - 阻塞 - 死亡
的過程赶袄。
- 新建:就是初始化線程對(duì)象
- 就緒:向線程對(duì)象發(fā)送start消息,線程對(duì)象被加入可調(diào)度線程池等待CPU調(diào)度抠藕。
- 運(yùn)行:CPU 負(fù)責(zé)調(diào)度可調(diào)度線程池中線程的執(zhí)行饿肺,線程執(zhí)行完成之前,狀態(tài)可能會(huì)在就緒和運(yùn)行之間來回切換盾似。就緒和運(yùn)行之間的狀態(tài)變化由CPU負(fù)責(zé)敬辣,程序員不能干預(yù)。
- 阻塞:當(dāng)滿足某個(gè)預(yù)定條件時(shí)零院,可以使用休眠或鎖溉跃,阻塞線程執(zhí)行
- 死亡:線程執(zhí)行完畢,退出门粪,銷毀喊积。
(1) NSThread
NSThread是蘋果官方提供面向?qū)ο蟛僮骶€程的技術(shù),簡(jiǎn)單方便玄妈,可以直接操作線程對(duì)象乾吻,不過需要自己控制線程的生命周期,我們看下蘋果官方給出的方法拟蜻。
[1] 初始化方法
- 實(shí)例初始化方法
- (instancetype)init API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
對(duì)應(yīng)的初始化方法:
//創(chuàng)建線程
NSThread *newThread = [[NSThread alloc]initWithTarget:self selector:@selector(demo:) object:@"Thread"];
NSThread *newThread=[[NSThread alloc]init];
NSThread *newThread= [[NSThread alloc]initWithBlock:^{
NSLog(@"Block");
}];
注意三種方法創(chuàng)建完成后都需要執(zhí)行
[newThread start]
去啟動(dòng)線程绎签。
- 類初始化方法
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
注意這兩個(gè)類方法創(chuàng)建后就可執(zhí)行,不需手動(dòng)開啟
[2] 取消退出
既然有了創(chuàng)建酝锅,那就得有退出
// 實(shí)例方法 取消線程
- (void)cancel;
//類方法 退出
+ (void)exit;
[3] 線程執(zhí)行狀態(tài)
// 線程正在執(zhí)行
@property (readonly, getter=isExecuting) BOOL executing API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
// 線程執(zhí)行結(jié)束
@property (readonly, getter=isFinished) BOOL finished API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
// 線程是否可取消
@property (readonly, getter=isCancelled) BOOL cancelled API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
[4] 線程間的通信方法
@interface NSObject (NSThreadPerformAdditions)
/*
* 去主線程執(zhí)行指定方法
* aSelector: 方法
* arg: 參數(shù)
* wait:表示是否等待主線程做完事情后往下走诡必,YES表示做完后執(zhí)行下面事情,NO表示跟下面事情一起執(zhí)行
*/
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
/*
* 去指定線程執(zhí)行指定方法
* aSelector: 方法
* arg: 參數(shù)
* wait:表示是否等待本線程做完事情后往下走搔扁,YES表示做完后執(zhí)行下面事爸舒,NO表示跟下面事一起執(zhí)行
*/
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
/*
* 去開啟的子線程執(zhí)行指定方法
* SEL: 方法
* arg: 參數(shù)
*/
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
我們常說的線程間的通信所用的方法其實(shí)就是上面的這幾個(gè)方法,所有繼承NSObject實(shí)例化對(duì)象都可調(diào)用稿蹲。當(dāng)然還有其他方法也可以實(shí)現(xiàn)線程間的通信扭勉,如:GCD
、NSOperation
苛聘、NSMachPort
端口等形式涂炎,我們后面用到在做介紹忠聚。
舉個(gè)簡(jiǎn)單的例子:我們?cè)谧泳€程中下載圖片,然后去主線程展示:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 子線程執(zhí)行下載方法
[self performSelectorInBackground:@selector(download) withObject:nil];
}
- (void)download{
//圖片的網(wǎng)絡(luò)路徑
NSURL *url = [NSURL URLWithString:@"https://p3.ssl.qhimg.com/t011e94f0b9ed8e66b0.png"];
//下載圖片數(shù)據(jù)
NSData *data = [NSData dataWithContentsOfURL:url];
//生成圖片
UIImage *image = [UIImage imageWithData:data];
// 回主線程顯示圖片
[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
}
- (void)showImage:(UIImage *)image{
self.imageView.image = image;
}
[5] 其他常用方法
-
+(void)currentThread
獲取當(dāng)前線程 -
+(BOOL)isMultiThreaded
判斷當(dāng)前是否運(yùn)行在子線程 -
-(BOOL)isMainThread
判斷是否在主線程 -
+(void)sleepUntilDate:(NSDate *)date;+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
當(dāng)前線程休眠時(shí)間
(2) GCD
在介紹GCD
前我們先來了解下多線程中比較容易混淆的幾個(gè)概念
[1]. 同步唱捣、異步两蟀、并發(fā)(并行)、串行
同步和異步主要影響:能不能開啟新的線程
同步:在當(dāng)前線程中執(zhí)行任務(wù)震缭,不具備開啟新線程的能力
異步:在新的線程中執(zhí)行任務(wù)赂毯,具備開啟新線程的能力并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
并發(fā):也叫并行,也叫并行隊(duì)列蛀序,多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行
串行:也叫串行隊(duì)列欢瞪,一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)
單純的介紹概念比較抽象徐裸,我們還是結(jié)合實(shí)際使用來說明:
[2] GCD 中的同步、異步方法
- 同步執(zhí)行方法:
dispatch_sync()
- 異步執(zhí)行方法:
dispatch_async()
使用方法:
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
可以看到這個(gè)兩個(gè)方法需要兩個(gè)參數(shù)啸盏,第一個(gè)參數(shù)需要傳入一個(gè)dispatch_queue_t
類型的隊(duì)列重贺,第二個(gè)是執(zhí)行的block。下面介紹一下GCD的隊(duì)列
[3] GCD 中的隊(duì)列
GCD中的隊(duì)列有三種:串行隊(duì)列回懦、并行隊(duì)列气笙、主隊(duì)列
,創(chuàng)建方式也非常簡(jiǎn)單:
- 串行隊(duì)列
dispatch_queue_t queue1 = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
第一個(gè)參數(shù)是隊(duì)列名稱怯晕,第二個(gè)是一個(gè)宏定義潜圃,常用的兩個(gè)宏 DISPATCH_QUEUE_SERIAL
和 DISPATCH_QUEUE_CONCURRENT
分別表示串行隊(duì)列和并行隊(duì)列,除此之外,宏DISPATCH_QUEUE_SERIAL_INACTIVE
和 DISPATCH_QUEUE_CONCURRENT_INACTIVE
分別表示初始化的串行隊(duì)列和并行隊(duì)列處于不可活動(dòng)狀態(tài)舟茶√菲冢看下它的底層實(shí)現(xiàn)
dispatch_queue_attr_t
dispatch_queue_attr_make_initially_inactive(
dispatch_queue_attr_t _Nullable attr);
#define DISPATCH_QUEUE_SERIAL_INACTIVE \
dispatch_queue_attr_make_initially_inactive(DISPATCH_QUEUE_SERIAL)
#define DISPATCH_QUEUE_CONCURRENT_INACTIVE \
dispatch_queue_attr_make_initially_inactive(DISPATCH_QUEUE_CONCURRENT)
應(yīng)當(dāng)注意的是,初始化后處于不可活動(dòng)狀態(tài)的隊(duì)列吧凉,添加到其中的任務(wù)要想開始執(zhí)行隧出,必須先調(diào)用
dispatch_activate()
函數(shù)使其狀態(tài)變更為可活動(dòng)狀態(tài).
- 并行隊(duì)列
并行隊(duì)列有兩種:
第一種:全局并發(fā)隊(duì)列創(chuàng)建方法,也是系統(tǒng)為我們創(chuàng)建好的并發(fā)隊(duì)列,創(chuàng)建方式
/* - QOS_CLASS_USER_INTERACTIVE
* - QOS_CLASS_USER_INITIATED
* - QOS_CLASS_DEFAULT
* - QOS_CLASS_UTILITY
* - QOS_CLASS_BACKGROUND
*/
//dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
這里有兩個(gè)參數(shù),第一個(gè)參數(shù)標(biāo)識(shí)線程執(zhí)行優(yōu)先級(jí)阀捅,第二個(gè)是蘋果保留參數(shù)傳參:0 就可以胀瞪。
第二種:手動(dòng)創(chuàng)建并發(fā)隊(duì)列
// 串行執(zhí)行,第一個(gè)參數(shù)是名稱 饲鄙,第二個(gè)是標(biāo)識(shí):DISPATCH_QUEUE_CONCURRENT,并發(fā)隊(duì)列標(biāo)識(shí)
dispatch_queue_t queue = dispatch_queue_create("myQueue",DISPATCH_QUEUE_CONCURRENT);
- 主隊(duì)列
主隊(duì)列是一種特殊的串行隊(duì)列
dispatch_queue_t queue = dispatch_get_main_queue();
同步凄诞、異步以及隊(duì)列的組合就可以實(shí)現(xiàn)對(duì)任務(wù)進(jìn)行多線程編程的需求了。
- 同步串行隊(duì)列
dispatch_queue_t queue1 = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
for(NSInteger i = 0; i < 10; i++){
dispatch_sync(queue1, ^{
NSLog(@"thread == %@ i====%ld",[NSThread currentThread],(long)i);
});
}
//thread == <NSThread: 0x6000011b8880>{number = 1, name = main} i====n
可以看到?jīng)]有開啟新的線程忍级,都是在主線程中執(zhí)行任務(wù),并且是順序執(zhí)行的
- 同步并行隊(duì)列
dispatch_queue_t queue1 = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
for(NSInteger i = 0; i < 10; i++){
dispatch_sync(queue1, ^{
NSLog(@"thread == %@ i====%ld",[NSThread currentThread],(long)i);
});
}
// thread == <NSThread: 0x600001db8a00>{number = 1, name = main} i====n
也是在主線程中順序執(zhí)行帆谍。
- 異步串行隊(duì)列
dispatch_queue_t queue1 = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
for(NSInteger i = 0; i < 10; i++){
dispatch_async(queue1, ^{
NSLog(@"thread == %@ i====%ld",[NSThread currentThread],(long)i);
});
}
開啟子線程,順序執(zhí)行任務(wù)
- 異步并發(fā)隊(duì)列
dispatch_queue_t queue1 = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
for(NSInteger i = 0; i < 10; i++){
dispatch_async(queue1, ^{
NSLog(@"thread == %@ i====%ld",[NSThread currentThread],(long)i);
});
}
/*
thread == <NSThread: 0x6000024f9440>{number = 4, name = (null)} i====0
thread == <NSThread: 0x6000024f5340>{number = 5, name = (null)} i====2
thread == <NSThread: 0x6000024a8780>{number = 3, name = (null)} i====3
thread == <NSThread: 0x6000024ac6c0>{number = 6, name = (null)} i====1
thread == <NSThread: 0x6000024f4a80>{number = 8, name = (null)} i====5
thread == <NSThread: 0x6000024b0b40>{number = 7, name = (null)} i====4
thread == <NSThread: 0x60000249cd00>{number = 9, name = (null)} i====6
thread == <NSThread: 0x6000024b0980>{number = 10, name = (null)} i====7
thread == <NSThread: 0x6000024cb900>{number = 11, name = (null)} i====8
thread == <NSThread: 0x6000024f5340>{number = 5, name = (null)} i====9
*/
開啟了多個(gè)子線程颤练,并且是并發(fā)執(zhí)行任務(wù)既忆。
注意 dispatch_async()
具備開辟新線程的能力驱负,但是不表示使用它就一定會(huì)開辟新的線程。 例如 傳入的 queue 是主隊(duì)列,就是在主線程中執(zhí)行任務(wù),沒有開辟新線程患雇。
dispatch_queue_t queue1 = dispatch_get_main_queue();
for(NSInteger i = 0; i < 10; i++){
sleep(2);
dispatch_async(queue1, ^{
NSLog(@"thread == %@ i====%ld",[NSThread currentThread],(long)i);
});
}
//thread == <NSThread: 0x600002b24880>{number = 1, name = main} i====n
主隊(duì)列是一種特殊的串行隊(duì)列跃脊,從打印結(jié)果看出,這里執(zhí)行方式是串行苛吱,而且沒有開啟新的線程酪术。
具體任務(wù)的執(zhí)行方式可以參考下面的表格
[4] dispatch_ group_ t 隊(duì)列組
dispatch_group_t
是一個(gè)比較實(shí)用的方法,通過構(gòu)造一個(gè)組的形式翠储,將各個(gè)同步或異步提交任務(wù)都加入到同一個(gè)組中绘雁,當(dāng)所有任務(wù)都完成后會(huì)收到通知,用于進(jìn)一步處理.舉個(gè)簡(jiǎn)單的例子如下:
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, concurrentQueue, ^{
for (int i = 0; i < 10; i++)
{
NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
}
});
dispatch_group_async(group, dispatch_get_main_queue(), ^{
for (int i = 0; i < 10; i++)
{
NSLog(@"Task2 %@ %d", [NSThread currentThread], i);
}
});
dispatch_group_async(group, concurrentQueue, ^{
for (int i = 0; i < 10; i++)
{
NSLog(@"Task3 %@ %d", [NSThread currentThread], i);
}
});
dispatch_group_notify(group, concurrentQueue, ^{
NSLog(@"All Task Complete");
});
[5] diapatch_barrier_async 柵欄異步調(diào)用函數(shù)
有異步調(diào)用就也有同步調(diào)用函數(shù)diapatch_barrier_sync()
援所,兩者的區(qū)別:dispatch_barrier_sync
需要等待柵欄執(zhí)行完才會(huì)執(zhí)行柵欄后面的任務(wù),而dispatch_barrier_async
無需等待柵欄執(zhí)行完,會(huì)繼續(xù)往下走庐舟,有什么用呢?其實(shí)柵欄函數(shù)用的最多的地方還是實(shí)現(xiàn)線程同步使用住拭,比如我們有這樣一個(gè)需求:怎么樣利用GCD實(shí)現(xiàn)多讀單寫文件的IO操作挪略?也就是怎么樣實(shí)現(xiàn)多讀單寫,看代碼:
@interface UserCenter()
{
// 定義一個(gè)并發(fā)隊(duì)列
dispatch_queue_t concurrent_queue;
// 用戶數(shù)據(jù)中心, 可能多個(gè)線程需要數(shù)據(jù)訪問
NSMutableDictionary *userCenterDic;
}
// 多讀單寫模型
@implementation UserCenter
- (id)init
{
self = [super init];
if (self) {
// 通過宏定義 DISPATCH_QUEUE_CONCURRENT 創(chuàng)建一個(gè)并發(fā)隊(duì)列
concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 創(chuàng)建數(shù)據(jù)容器
userCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
- (id)objectForKey:(NSString *)key
{
__block id obj;
// 同步讀取指定數(shù)據(jù),立刻返回讀取結(jié)果
dispatch_sync(concurrent_queue, ^{
obj = [userCenterDic objectForKey:key];
});
return obj;
}
- (void)setObject:(id)obj forKey:(NSString *)key
{
// 異步柵欄調(diào)用設(shè)置數(shù)據(jù)
dispatch_barrier_async(concurrent_queue, ^{
[userCenterDic setObject:obj forKey:key];
});
}
可以看到把寫操作放入柵欄函數(shù)滔岳,可以實(shí)現(xiàn)線程同步效果
注意:使用dispatch_barrier_async
杠娱,該函數(shù)只能搭配自定義并發(fā)隊(duì)列dispatch_queue_t
使用。不能使用全局并發(fā)隊(duì)列:dispatch_get_global_queue
谱煤,否則dispatch_barrier_async
無作用摊求。
[6] 線程死鎖
先來看兩個(gè)例子:
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"執(zhí)行任務(wù)2");
});// 往主線程里面 同步添加任務(wù) 會(huì)發(fā)生死鎖現(xiàn)象
dispatch_queue_t myQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(myQueue, ^{
NSLog(@"1111,thread====%@",[NSThread currentThread]);
dispatch_sync(myQueue, ^{
NSLog(@"2222,thread====%@",[NSThread currentThread]);
});
});
// 1111,thread====<NSThread: 0x6000022dd880>{number = 5, name = (null)}
// crash
上面的例子可以看出,不能向當(dāng)前的串行隊(duì)列刘离,同步添加任務(wù)室叉,否則會(huì)產(chǎn)生死鎖導(dǎo)致crash。線程死鎖的條件:使用sync函數(shù)往當(dāng)前串行隊(duì)列里面添加任務(wù)寥闪,會(huì)產(chǎn)生死鎖太惠。
(3) NSOperation
NSOperation 是蘋果對(duì)GCD面向?qū)ο蟮姆庋b,它的底層是基于GCD
實(shí)現(xiàn)的疲憋,相比于GCD它添加了更多實(shí)用的功能
- 可以添加任務(wù)依賴
- 任務(wù)執(zhí)行狀態(tài)的控制
- 設(shè)置最大并發(fā)數(shù)
它有兩個(gè)核心類分別是NSOperation
和NSOperationQueue
凿渊,NSOperation就是對(duì)任務(wù)進(jìn)行的封裝,封裝好的任務(wù)交給不同的NSOperationQueue即可進(jìn)行串行隊(duì)列的執(zhí)行或并發(fā)隊(duì)列的執(zhí)行缚柳。
[1] NSOperation
NSOperation 是一個(gè)抽象類埃脏,并不能直接使用,必須使用它的子類秋忙,有三種方式:NSInvocationOperation
彩掐、NSBlockOperation
、自定義子類繼承NSOperation
,前兩種是蘋果為我們封裝好的灰追,可以直接使用堵幽,自定義子類狗超,需要我們實(shí)現(xiàn)相應(yīng)的方法。
- NSBlockOperation & NSInvocationOperation
使用:
//創(chuàng)建一個(gè)NSBlockOperation對(duì)象朴下,傳入一個(gè)block
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++)
{
NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
}
}];
/*
創(chuàng)建一個(gè)NSInvocationOperation對(duì)象努咐,指定執(zhí)行的對(duì)象和方法
該方法可以接收一個(gè)參數(shù)即object
*/
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"Hello, World!"];
// 執(zhí)行
[operation start];
[invocationOperation start];
// 打印: Task1 <NSThread: 0x6000019581c0>{number = 1, name = main} 0
可以看到創(chuàng)建這兩個(gè)任務(wù)對(duì)象去執(zhí)行任務(wù)殴胧,并沒有開啟新線程渗稍。NSBlockOperation 相比 NSInvocationOperation 多了個(gè)addExecutionBlock
追加任務(wù)的方法,
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++)
{
NSLog(@"task1=====%@ %d", [NSThread currentThread], i);
}
}];
[operation addExecutionBlock:^{
NSLog(@"task2=====%@",[NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"task3=====%@",[NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"task4=====%@",[NSThread currentThread]);
}];
[operation start];
/*
task3=====<NSThread: 0x600000509840>{number = 6, name = (null)}
task4=====<NSThread: 0x600000530200>{number = 3, name = (null)}
task1=====<NSThread: 0x600000558880>{number = 1, name = main} 0
task2=====<NSThread: 0x600000511680>{number = 5, name = (null)}
task1=====<NSThread: 0x600000558880>{number = 1, name = main} 1
task1=====<NSThread: 0x600000558880>{number = 1, name = main} 2
task1=====<NSThread: 0x600000558880>{number = 1, name = main} 3
task1=====<NSThread: 0x600000558880>{number = 1, name = main} 4
*/
使用
addExecutionBlock
追加的任務(wù)是并發(fā)執(zhí)行的,如果這個(gè)操作的任務(wù)數(shù)大于1那么會(huì)開啟子線程并發(fā)執(zhí)行任務(wù)团滥,這里追加的任務(wù)不一定就是子線程竿屹,也有可能是主線程。
[2] NSOperationQueue
NSOperationQueue 有兩種隊(duì)列灸姊,一個(gè)是主隊(duì)列通過[NSOperationQueue mainQueue]
獲取拱燃,還有一個(gè)是自己創(chuàng)建的隊(duì)列[[NSOperationQueue alloc] init]
,它同時(shí)具備并發(fā)跟串行的能力,可以通過設(shè)置最大并發(fā)數(shù)來決定力惯。
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++)
{
NSLog(@"task1=====%@ %d", [NSThread currentThread], i);
}
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++)
{
NSLog(@"Task2===== %@ %d", [NSThread currentThread], i);
}
}];
NSOperationQueue *queues = [[NSOperationQueue alloc] init];
[queues setMaxConcurrentOperationCount:2];//設(shè)置最大并發(fā)數(shù)扼雏,如果設(shè)置為1則串行執(zhí)行
[queues addOperation:operation];
[queues addOperation:operation2];
/*
Task2===== <NSThread: 0x600000489940>{number = 4, name = (null)} 0
task1=====<NSThread: 0x6000004e15c0>{number = 5, name = (null)} 0
*/
這個(gè)例子有兩個(gè)任務(wù),如果設(shè)置最大并發(fā)數(shù)為2夯膀,則會(huì)開辟兩個(gè)線程,并發(fā)執(zhí)行這兩個(gè)任務(wù)苍蔬。如果設(shè)置為1诱建,則會(huì)在新的線程中串行執(zhí)行。
[3] 任務(wù)依賴
addDependency
可以建立兩個(gè)任務(wù)之間的依賴關(guān)系碟绑,如[operation2 addDependency:operation1];
為任務(wù)2依賴任務(wù)1俺猿,必須等任務(wù)1執(zhí)行完成后才會(huì)執(zhí)行任務(wù)2,看個(gè)例子
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++)
{
NSLog(@"task1=====%@ %d", [NSThread currentThread], i);
}
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++)
{
NSLog(@"Task2===== %@ %d", [NSThread currentThread], i);
}
}];
NSOperationQueue *queues = [[NSOperationQueue alloc] init];
[queues setMaxConcurrentOperationCount:2];
//設(shè)置任務(wù)依賴
[operation addDependency:operation2];
[queues addOperation:operation];
[queues addOperation:operation2];
/*
Task2===== <NSThread: 0x6000005b5dc0>{number = 6, name = (null)} 0
Task2===== <NSThread: 0x6000005b5dc0>{number = 6, name = (null)} 1
Task2===== <NSThread: 0x6000005b5dc0>{number = 6, name = (null)} 2
Task2===== <NSThread: 0x6000005b5dc0>{number = 6, name = (null)} 3
Task2===== <NSThread: 0x6000005b5dc0>{number = 6, name = (null)} 4
task1=====<NSThread: 0x6000005b5dc0>{number = 6, name = (null)} 0
task1=====<NSThread: 0x6000005b5dc0>{number = 6, name = (null)} 1
task1=====<NSThread: 0x6000005b5dc0>{number = 6, name = (null)} 2
task1=====<NSThread: 0x6000005b5dc0>{number = 6, name = (null)} 3
task1=====<NSThread: 0x6000005b5dc0>{number = 6, name = (null)} 4
*/
還是以上面的例子格仲,設(shè)置[operation addDependency:operation2];
,可以看到任務(wù)2完成后才會(huì)執(zhí)行任務(wù)1的操作押袍。
[4] 自定義NSOperation
任務(wù)執(zhí)行狀態(tài)的控制是相對(duì)于自定義的NSOperation子類來說的。對(duì)于自定義NSOperation子類有兩種類型:
- 重寫
main
方法
只重寫operation
的main方法,main方法里面寫要執(zhí)行的任務(wù)凯肋,系統(tǒng)底層控制變更任務(wù)執(zhí)行完成狀態(tài)谊惭,以及任務(wù)的退出∥甓看個(gè)例子
#import "TestOperation.h"
@interface TestOperation ()
@property (nonatomic, copy) id obj;
@end
@implementation TestOperation
- (instancetype)initWithObject:(id)obj{
if(self = [super init]){
self.obj = obj;
}
return self;
}
- (void)main{
NSLog(@"開始執(zhí)行任務(wù)%@ thread===%@",self.obj,[NSThread currentThread]);
}
調(diào)用
TestOperation *operation4 = [[TestOperation alloc] initWithObject:[NSString stringWithFormat:@"我是任務(wù)4"]];
[operation4 setCompletionBlock:^{
NSLog(@"執(zhí)行完成 thread===%@",[NSThread currentThread]);
}];
[operation4 start];
// 打印
開始執(zhí)行任務(wù)我是任務(wù)4 thread===<NSThread: 0x6000008d8880>{number = 1, name = main}
執(zhí)行完成 thread===<NSThread: 0x60000089fa40>{number = 7, name = (null)}
可以看到任務(wù)operation的main方法執(zhí)行是在主線程中的圈盔,只是最后完成后的回調(diào)setCompletionBlock
是異步的,好像沒什么用悄雅,別著急驱敲,我們把他放入隊(duì)列中執(zhí)行看下,還是上面的例子,加入隊(duì)列執(zhí)行
NSOperationQueue *queue4 = [[NSOperationQueue alloc] init];
TestOperation *operation4 = [[TestOperation alloc] initWithObject:[NSString stringWithFormat:@"我是任務(wù)4"]];
TestOperation *operation5 = [[TestOperation alloc] initWithObject:[NSString stringWithFormat:@"我是任務(wù)5"]];
TestOperation *operation6 = [[TestOperation alloc] initWithObject:[NSString stringWithFormat:@"我是任務(wù)6"]];
[queue4 addOperation:operation4];
[queue4 addOperation:operation5];
[queue4 addOperation:operation6];
//打涌硐小:
開始執(zhí)行任務(wù)我是任務(wù)6 thread===<NSThread: 0x600001fc8200>{number = 5, name = (null)}
開始執(zhí)行任務(wù)我是任務(wù)4 thread===<NSThread: 0x600001fcc040>{number = 6, name = (null)}
開始執(zhí)行任務(wù)我是任務(wù)5 thread===<NSThread: 0x600001fd7c80>{number = 7, name = (null)}
這時(shí)候可以看到任務(wù)的并發(fā)執(zhí)行了众眨,operation的main方法執(zhí)行結(jié)束后就會(huì)調(diào)用各自的dealloc
方法進(jìn)行釋放握牧,任務(wù)的生命周期結(jié)束。如果我們想讓任務(wù)4娩梨、5庄蹋、6 倒序執(zhí)行,可以添加任務(wù)依賴
[operation4 addDependency:operation5];
[operation5 addDependency:operation6];
// 打印
開始執(zhí)行任務(wù)我是任務(wù)6 thread===<NSThread: 0x600003d04680>{number = 6, name = (null)}
開始執(zhí)行任務(wù)我是任務(wù)5 thread===<NSThread: 0x600003d04680>{number = 6, name = (null)}
開始執(zhí)行任務(wù)我是任務(wù)4 thread===<NSThread: 0x600003d04680>{number = 6, name = (null)}
這樣做貌似是可以的双藕,但是如果我們的operation 中又存在異步任務(wù)(如網(wǎng)絡(luò)請(qǐng)求)币励,我們想讓網(wǎng)絡(luò)任務(wù)6請(qǐng)求完后調(diào)用任務(wù)5,任務(wù)5調(diào)用成功后調(diào)任務(wù)4掸冤,那該怎么辦呢厘托,我們先賣個(gè)關(guān)子,我們?cè)诘诙?jié)多個(gè)請(qǐng)求完成后繼續(xù)進(jìn)行下一個(gè)請(qǐng)求的方法總結(jié)
中介紹稿湿。
- 重寫
start
方法
通過重寫main
方法可以實(shí)現(xiàn)任務(wù)的串行執(zhí)行铅匹,如果要讓任務(wù)并發(fā)執(zhí)行,就需要重寫start
方法饺藤。兩者還是有很大區(qū)別的:
如果只是重寫main方法包斑,方法執(zhí)行完畢,那么整個(gè)operation就會(huì)從隊(duì)列中被移除涕俗。如果你是一個(gè)自定義的operation并且它是某些類的代理罗丰,這些類恰好有異步方法,這時(shí)就會(huì)找不到代理導(dǎo)致程序出錯(cuò)了再姑。然而start方法就算執(zhí)行完畢萌抵,它的finish屬性也不會(huì)變,因此你可以控制這個(gè)operation的生命周期了元镀。然后在任務(wù)完成之后手動(dòng)cancel掉這個(gè)operation即可绍填。
@interface TestStartOperation : NSOperation
- (instancetype)initWithObject:(id)obj;
@property (nonatomic, copy) id obj;
@property (nonatomic, assign, getter=isExecuting) BOOL executing;
@property (nonatomic, assign, getter=isFinished) BOOL finished;
@end
@implementation TestStartOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
- (instancetype)initWithObject:(id)obj{
if(self = [super init]){
self.obj = obj;
}
return self;
}
- (void)start{
//在任務(wù)開始前設(shè)置executing為YES,在此之前可能會(huì)進(jìn)行一些初始化操作
self.executing = YES;
NSLog(@"開始執(zhí)行任務(wù)%@ thread===%@",self.obj,[NSThread currentThread]);
/*
需要在適當(dāng)?shù)奈恢门袛嗤獠渴欠裾{(diào)用了cancel方法
如果被cancel了需要正確的結(jié)束任務(wù)
*/
if (self.isCancelled)
{
//任務(wù)被取消正確結(jié)束前手動(dòng)設(shè)置狀態(tài)
self.executing = NO;
self.finished = YES;
return;
}
NSString *str = @"https://www.#";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
__weak typeof(self) weakSelf = self;
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// NSLog(@"response==%@",response);
NSLog(@"TASK完成:====%@ thread====%@",weakSelf.obj,[NSThread currentThread]);
//任務(wù)執(zhí)行完成后手動(dòng)設(shè)置狀態(tài)
weakSelf.executing = NO;
weakSelf.finished = YES;
}];
[task resume];
}
- (void)setExecuting:(BOOL)executing
{
//手動(dòng)調(diào)用KVO通知
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
//調(diào)用KVO通知
[self didChangeValueForKey:@"isExecuting"];
}
- (BOOL)isExecuting
{
return _executing;
}
- (void)setFinished:(BOOL)finished
{
//手動(dòng)調(diào)用KVO通知
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
//調(diào)用KVO通知
[self didChangeValueForKey:@"isFinished"];
}
- (BOOL)isFinished
{
return _finished;
}
- (BOOL)isAsynchronous
{
return YES;
}
- (void)dealloc{
NSLog(@"Dealloc %@",self.obj);
}
執(zhí)行與結(jié)果
NSOperationQueue *queue4 = [[NSOperationQueue alloc] init];
TestStartOperation *operation4 = [[TestStartOperation alloc] initWithObject:[NSString stringWithFormat:@"我是任務(wù)4"]];
TestStartOperation *operation5 = [[TestStartOperation alloc] initWithObject:[NSString stringWithFormat:@"我是任務(wù)5"]];
TestStartOperation *operation6 = [[TestStartOperation alloc] initWithObject:[NSString stringWithFormat:@"我是任務(wù)6"]];
//設(shè)置任務(wù)依賴
[operation4 addDependency:operation5];
[operation5 addDependency:operation6];
[queue4 addOperation:operation4];
[queue4 addOperation:operation5];
[queue4 addOperation:operation6];
/*打印
開始執(zhí)行任務(wù)我是任務(wù)6 thread===<NSThread: 0x600002bb8480>{number = 6, name = (null)}
TASK完成:====我是任務(wù)6 thread====<NSThread: 0x600002bd4d80>{number = 8, name = (null)}
開始執(zhí)行任務(wù)我是任務(wù)5 thread===<NSThread: 0x600002bb0300>{number = 5, name = (null)}
TASK完成:====我是任務(wù)5 thread====<NSThread: 0x600002bb0300>{number = 5, name = (null)}
開始執(zhí)行任務(wù)我是任務(wù)4 thread===<NSThread: 0x600002bfb080>{number = 7, name = (null)}
TASK完成:====我是任務(wù)4 thread====<NSThread: 0x600002bfb080>{number = 7, name = (null)}
2021-06-22 17:57:56.436591+0800 Interview01-打印[15994:9172130] Dealloc 我是任務(wù)4
2021-06-22 17:57:56.436690+0800 Interview01-打印[15994:9172130] Dealloc 我是任務(wù)5
2021-06-22 17:57:56.436784+0800 Interview01-打印[15994:9172130] Dealloc 我是任務(wù)6
*/
在這個(gè)例子中我們?cè)谌蝿?wù)請(qǐng)求完成后栖疑,手動(dòng)設(shè)置其self.executing
和self.finished
狀態(tài)讨永,并且手動(dòng)觸發(fā)KVO,隊(duì)列會(huì)監(jiān)聽任務(wù)的執(zhí)行狀態(tài)遇革。由于我們?cè)O(shè)置了任務(wù)依賴卿闹,當(dāng)任務(wù)6請(qǐng)求完成后才會(huì)執(zhí)行任務(wù)5,任務(wù)5請(qǐng)求完成后 才會(huì)執(zhí)行任務(wù)4澳淑。最后對(duì)各自任務(wù)進(jìn)行移除隊(duì)列并釋放比原。其實(shí)這樣也變相解決了上面重寫main
方法中無法解決的問題。
二.實(shí)際應(yīng)用
多個(gè)請(qǐng)求完成后繼續(xù)進(jìn)行下一個(gè)請(qǐng)求的方法總結(jié)
在我們的工作中經(jīng)常會(huì)遇到這樣的請(qǐng)求:一個(gè)請(qǐng)求依賴另一個(gè)請(qǐng)求的結(jié)果杠巡,或者多個(gè)請(qǐng)求一起發(fā)出然后再獲取所有的結(jié)果后繼續(xù)后續(xù)操作量窘。根據(jù)這幾種情況總結(jié)常用的方法:
1. 使用GCD
的dispatch_group_t
實(shí)現(xiàn)
需求:請(qǐng)求順序執(zhí)行,執(zhí)行完成后回調(diào)結(jié)果
NSString *str = @"https://www.#";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
dispatch_group_t downloadGroup = dispatch_group_create();
for (int i=0; i<10; i++) {
dispatch_group_enter(downloadGroup);
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"執(zhí)行完請(qǐng)求=%d",i);
dispatch_group_leave(downloadGroup);
}];
[task resume];
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
NSLog(@"end");
});
/*
2021-06-22 18:37:56.786878+0800 Interview01-打印[17121:9352056] 請(qǐng)求結(jié)束:0
2021-06-22 18:37:56.787770+0800 Interview01-打印[17121:9352057] 請(qǐng)求結(jié)束:1
2021-06-22 18:37:56.788492+0800 Interview01-打印[17121:9352057] 請(qǐng)求結(jié)束:2
2021-06-22 18:37:56.789148+0800 Interview01-打印[17121:9352057] 請(qǐng)求結(jié)束:3
2021-06-22 18:37:56.789837+0800 Interview01-打印[17121:9352057] 請(qǐng)求結(jié)束:4
2021-06-22 18:37:56.790433+0800 Interview01-打印[17121:9352059] 請(qǐng)求結(jié)束:5
2021-06-22 18:37:56.791117+0800 Interview01-打印[17121:9352059] 請(qǐng)求結(jié)束:6
2021-06-22 18:37:56.791860+0800 Interview01-打印[17121:9352059] 請(qǐng)求結(jié)束:7
2021-06-22 18:37:56.792614+0800 Interview01-打印[17121:9352059] 請(qǐng)求結(jié)束:8
2021-06-22 18:37:56.793201+0800 Interview01-打印[17121:9352059] 請(qǐng)求結(jié)束:9
2021-06-22 18:37:56.804529+0800 Interview01-打印[17121:9351753] end*/
主要方法:
-
dispatch_group_t downloadGroup = dispatch_group_create();
創(chuàng)建隊(duì)列組 -
dispatch_group_enter(downloadGroup);
每次執(zhí)行請(qǐng)求前調(diào)用 -
dispatch_group_leave(downloadGroup);
請(qǐng)求完成后調(diào)用離開方法 -
dispatch_group_notify()
所有請(qǐng)求完成后回調(diào)block - 對(duì)于enter和leave必須配合使用氢拥,有幾次enter就要有幾次leave
2. GCD
信號(hào)量dispatch_semaphore_t
(1).需求:順序執(zhí)行多個(gè)請(qǐng)求蚌铜,都執(zhí)行完成后回調(diào)給end
NSString *str = @"https://www.#";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"請(qǐng)求結(jié)束:%d",i);
dispatch_semaphore_signal(sem);
}];
[task resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"end");
});
主要方法
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_semaphore_signal(sem);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore
信號(hào)量為基于計(jì)數(shù)器的一種多線程同步機(jī)制,dispatch_semaphore_signal(sem);
表示為計(jì)數(shù)+1操作锨侯,dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
信號(hào)量-1,遇到dispatch_semaphore_wait
如果信號(hào)量的值小于0,就一直阻塞線程,不執(zhí)行后面的所有程序,直到信號(hào)量大于等于0;當(dāng)?shù)谝粋€(gè)for循環(huán)執(zhí)行后dispatch_semaphore_wait
堵塞線程冬殃,直到執(zhí)行到dispatch_semaphore_signal
后繼續(xù)下一個(gè)for循環(huán)進(jìn)行請(qǐng)求囚痴,以此類推完成順序請(qǐng)求。
(2).需求:多個(gè)請(qǐng)求同時(shí)進(jìn)行审葬,都執(zhí)行完成后回調(diào)給end
NSString *str = @"https://www.#";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
__block int count = 0;
for (int i=0; i<10; i++) {
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%d---%d",i,i);
count++;
if (count==10) {
dispatch_semaphore_signal(sem);
count = 0;
}
}];
[task resume];
}
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"end");
});
/*
2021-06-23 09:47:49.723576+0800 Interview01-打印[21740:9823752] 請(qǐng)求完成:0
2021-06-23 09:47:49.741118+0800 Interview01-打印[21740:9823751] 請(qǐng)求完成:1
2021-06-23 09:47:49.756781+0800 Interview01-打印[21740:9823752] 請(qǐng)求完成:3
2021-06-23 09:47:49.765250+0800 Interview01-打印[21740:9823752] 請(qǐng)求完成:2
2021-06-23 09:47:49.773008+0800 Interview01-打印[21740:9823756] 請(qǐng)求完成:4
2021-06-23 09:47:49.797809+0800 Interview01-打印[21740:9823751] 請(qǐng)求完成:5
2021-06-23 09:47:49.801775+0800 Interview01-打印[21740:9823751] 請(qǐng)求完成:6
2021-06-23 09:47:49.805542+0800 Interview01-打印[21740:9823751] 請(qǐng)求完成:7
2021-06-23 09:47:49.814714+0800 Interview01-打印[21740:9823751] 請(qǐng)求完成:8
2021-06-23 09:47:49.850517+0800 Interview01-打印[21740:9823753] 請(qǐng)求完成:9
2021-06-23 09:47:49.864394+0800 Interview01-打印[21740:9823591] end
*/
這個(gè)也比較好理解深滚,for循環(huán)運(yùn)行后堵塞當(dāng)前線程(當(dāng)前是主線程,你也可以把這段代碼放入子線程中去執(zhí)行)涣觉,當(dāng)10個(gè)請(qǐng)求全部完成后發(fā)送信號(hào)痴荐,繼續(xù)下面的流程。
3. 使用NSOperation
與GCD
結(jié)合使用
需求:兩個(gè)網(wǎng)絡(luò)請(qǐng)求官册,第一個(gè)依賴第二個(gè)的回調(diào)結(jié)果
通過自定義operation
實(shí)現(xiàn)生兆,我們重寫其main方法
@interface CustomOperation : NSOperation
@property (nonatomic, copy) id obj;
- (instancetype)initWithObject:(id)obj;
@end
@implementation CustomOperation
- (instancetype)initWithObject:(id)obj{
if(self = [super init]){
self.obj = obj;
}
return self;
}
- (void)main{
//創(chuàng)建信號(hào)量并設(shè)置計(jì)數(shù)默認(rèn)為0
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSLog(@"開始執(zhí)行任務(wù)%@",self.obj);
NSString *str = @"https://www.#";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"TASK完成:====%@ thread====%@",self.obj,[NSThread currentThread]);
//請(qǐng)求成功 計(jì)數(shù)+1操作
dispatch_semaphore_signal(sema);
}];
[task resume];
//若計(jì)數(shù)為0則一直等待
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
調(diào)用與結(jié)果
NSOperationQueue *queue3 = [[NSOperationQueue alloc] init];
[queue3 setMaxConcurrentOperationCount:2];
CustomOperation *operation0 = [[CustomOperation alloc] initWithObject:@"我是任務(wù)0"];
CustomOperation *operation1 = [[CustomOperation alloc] initWithObject:@"我是任務(wù)1"];
CustomOperation *operation2 = [[CustomOperation alloc] initWithObject:@"我是任務(wù)2"];
CustomOperation *operation3 = [[CustomOperation alloc] initWithObject:@"我是任務(wù)3"];
[operation0 addDependency:operation1];
[operation1 addDependency:operation2];
[operation2 addDependency:operation3];
[queue3 addOperation:operation0];
[queue3 addOperation:operation1];
[queue3 addOperation:operation2];
[queue3 addOperation:operation3];
/**打印結(jié)果
開始執(zhí)行任務(wù)我是任務(wù)3
TASK完成:====我是任務(wù)3 thread====<NSThread: 0x6000039c3340>{number = 5, name = (null)}
開始執(zhí)行任務(wù)我是任務(wù)2
TASK完成:====我是任務(wù)2 thread====<NSThread: 0x6000039ece80>{number = 7, name = (null)}
開始執(zhí)行任務(wù)我是任務(wù)1
TASK完成:====我是任務(wù)1 thread====<NSThread: 0x6000039c3340>{number = 5, name = (null)}
開始執(zhí)行任務(wù)我是任務(wù)0
TASK完成:====我是任務(wù)0 thread====<NSThread: 0x6000039c3d00>{number = 6, name = (null)}
*/
- 設(shè)置任務(wù)依賴并且添加到隊(duì)列后是可以滿足我們的需求
- 由于任務(wù)內(nèi)部是異步回調(diào),可以看到任務(wù)內(nèi)部的執(zhí)行還是依賴于
dispatch_semaphore_t
來實(shí)現(xiàn)的 - 也可以通過重寫
start
方法實(shí)現(xiàn)膝宁,在上面章節(jié)我們已經(jīng)介紹過了鸦难,這里不再贅述。
三. 總結(jié)
本文的篇幅有點(diǎn)長了员淫,但是還有一些內(nèi)容沒有覆蓋到合蔽,比如iOS中常用的線程鎖、NSOperationQueue
的暫停與取消等介返,我們會(huì)在后面的文章中逐步完善補(bǔ)充辈末。
由于作者水平有限,難免出現(xiàn)紕漏映皆,如有問題還請(qǐng)不吝賜教。
參考資料: