相關(guān)代碼:
https://pan.baidu.com/s/1nvNdedz
https://pan.baidu.com/s/1qYF5vRI
請(qǐng)配合代碼閱讀,文章內(nèi)容來(lái)源于小馬哥視頻
Tips: 作者會(huì)不定期更新類似總結(jié)性的文章,如果你喜歡,請(qǐng)關(guān)注我。
多線程中的基本概念
1. 進(jìn)程
進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序。每個(gè)進(jìn)程之間是獨(dú)立的,每個(gè)進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存空間內(nèi)。
</br>
2. 線程
- 基本概念
- 1個(gè)進(jìn)程要想執(zhí)行任務(wù),必須得有線程(每1個(gè)進(jìn)程至少要有1條線程),線程是進(jìn)程的基本執(zhí)行單元,一個(gè)進(jìn)程(程序)的所有任務(wù)都在線程中執(zhí)行。
- 線程的串行
- 1個(gè)線程中任務(wù)的執(zhí)行是串行的,如果要在1個(gè)線程中執(zhí)行多個(gè)任務(wù),那么只能一個(gè)一個(gè)地按順序執(zhí)行這些任務(wù)。也就是說(shuō),在同一時(shí)間內(nèi),1個(gè)線程只能執(zhí)行1個(gè)任務(wù)。
</br>
3. 多線程
- 基本概念
- 即1個(gè)進(jìn)程中可以開(kāi)啟多條線程,每條線程可以并行(同時(shí))執(zhí)行不同的任務(wù)。
- 線程的并行
- 并行即同時(shí)執(zhí)行。比如同時(shí)開(kāi)啟3條線程分別下載3個(gè)文件(分別是文件A、文件B、文件C)。
- 多線程并發(fā)執(zhí)行的原理
- 在同一時(shí)間里,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)。多線程并發(fā)(同時(shí))執(zhí)行,其實(shí)是CPU快速地在多條線程之間調(diào)度(切換),如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象。
- 多線程優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn)
- 能適當(dāng)提高程序的執(zhí)行效率。
- 能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
- 缺點(diǎn)
- 開(kāi)啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占用1M,子線程占用512KB),如果開(kāi)啟大量的線程,會(huì)占用大量的內(nèi)存空間,降低程序的性能。
- 線程越多,CPU在調(diào)度線程上的開(kāi)銷就越大。
- 程序設(shè)計(jì)更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享
</br>
- 優(yōu)點(diǎn)
4. 多線程在iOS開(kāi)發(fā)中的應(yīng)用
- 主線程
- 一個(gè)iOS程序運(yùn)行后,默認(rèn)會(huì)開(kāi)啟1條線程,稱為“主線程”或“UI線程”。
- 作用,刷新顯示UI,處理UI事件
- 使用注意
- 不要將耗時(shí)操作放到主線程中去處理,會(huì)卡住線程。
- 和UI相關(guān)的刷新操作必須放到主線程中進(jìn)行處理
</br>
5. iOS中多線程的實(shí)現(xiàn)方案
pthread
-
特點(diǎn):
- 一套通用的多線程API
- 適用于Unix\Linux\Windows等系統(tǒng)
- 跨平臺(tái)\可移植
- 使用難度大 b.使用語(yǔ)言:c語(yǔ)言 c.使用頻率:幾乎不用 d.線程生命周期:由程序員進(jìn)行管理
-
NSThread
- 特點(diǎn):
- 使用更加面向?qū)ο?/li>
- 簡(jiǎn)單易用,可直接操作線程對(duì)象
- 使用語(yǔ)言:OC語(yǔ)言
- 使用頻率:偶爾使用
- 線程生命周期:由程序員進(jìn)行管理
- 特點(diǎn):
-
GCD
- 特點(diǎn):
- 旨在替代
NSThread等線程技術(shù) - 充分利用設(shè)備的多核(自動(dòng))
- 旨在替代
- 使用語(yǔ)言:C語(yǔ)言
- 使用頻率:經(jīng)常使用
- 線程生命周期:自動(dòng)管理
- 特點(diǎn):
-
NSOperation
- 特點(diǎn):
- 基于GCD(底層是GCD)
- 比GCD多了一些更簡(jiǎn)單實(shí)用的功能
- 使用更加面向?qū)ο?/li>
- 使用語(yǔ)言:OC語(yǔ)言
- 使用頻率:經(jīng)常使用
- 線程生命周期:自動(dòng)管理
</br>
- 特點(diǎn):
pthread[了解,主要是為了能看得懂三方庫(kù)的代碼]
#import <pthread.h>
//使用pthread創(chuàng)建線程對(duì)象
pthread_t thread;
NSString *name = @"wendingding";
//使用pthread創(chuàng)建線程
//第一個(gè)參數(shù):線程對(duì)象地址
//第二個(gè)參數(shù):線程屬性
//第三個(gè)參數(shù):指向函數(shù)的指針
//第四個(gè)參數(shù):傳遞給該函數(shù)的參數(shù)
pthread_create(&thread, NULL, run, (__bridge void *)(name));
</br>
NSThread
1. 基本用法
/*
第一種創(chuàng)建線程的方式:alloc init.
特點(diǎn):需要手動(dòng)開(kāi)啟線程,可以拿到線程對(duì)象進(jìn)行詳細(xì)設(shè)置
創(chuàng)建線程
第一個(gè)參數(shù):目標(biāo)對(duì)象
第二個(gè)參數(shù):選擇器,線程啟動(dòng)要調(diào)用哪個(gè)方法
第三個(gè)參數(shù):前面方法要接收的參數(shù)(最多只能接收一個(gè)參數(shù),沒(méi)有則傳nil)
**/
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"wendingding"];
//設(shè)置屬性
threadA.name = @"線程A";
//設(shè)置優(yōu)先級(jí) 取值范圍 0.0 ~ 1.0 之間 最高是1.0 默認(rèn)優(yōu)先級(jí)是0.5 優(yōu)先級(jí)越高,執(zhí)行的次數(shù)(可能性)越高
threadA.threadPriority = 1.0;
/**
啟動(dòng)線程
!!![線程處于就緒狀態(tài),所有被標(biāo)記為start的線程會(huì)被維護(hù)在一個(gè)池中,
這樣系統(tǒng)就會(huì)知道這個(gè)池中的所有線程都是需要被調(diào)用的。
然后CPU快速地在多條線程之間調(diào)度(切換),造成了多線程并發(fā)執(zhí)行的假象。]
*/
[thread start];
/*
第二種創(chuàng)建線程的方式:分離出一條子線程
特點(diǎn):自動(dòng)啟動(dòng)線程,無(wú)法對(duì)線程進(jìn)行更詳細(xì)的設(shè)置
第一個(gè)參數(shù):線程啟動(dòng)調(diào)用的方法
第二個(gè)參數(shù):目標(biāo)對(duì)象
第三個(gè)參數(shù):傳遞給調(diào)用方法的參數(shù)
**/
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"我是分離出來(lái)的子線程"];
/*
第三種創(chuàng)建線程的方式:后臺(tái)線程
!!!這是NSObject的類別方法,所有只要是NSObject的子類,都可以調(diào)用此方法!
特點(diǎn):自動(dòng)啟動(dòng)線程,無(wú)法進(jìn)行更詳細(xì)設(shè)置
**/
[self performSelectorInBackground:@selector(run:) withObject:@"我是后臺(tái)線程"];
</br>
2. 線程的狀態(tài)[了解]
//線程的各種狀態(tài):新建-就緒-運(yùn)行-阻塞-死亡
//常用的控制線程狀態(tài)的方法
[NSThread exit];
//退出當(dāng)前線程
[NSThread sleepForTimeInterval:2.0];
//阻塞線程
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
//阻塞線程
//注意:線程死了不能復(fù)生
</br>
3. 線程安全
- 前提:多個(gè)線程訪問(wèn)同一塊資源會(huì)發(fā)生數(shù)據(jù)安全問(wèn)題
- 解決方案:加互斥鎖
- 相關(guān)代碼:@synchronized(self){}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//設(shè)置中票數(shù)
self.totalCount = 100;
self.threadA = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.threadB = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.threadC = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.threadA.name = @"售票員A";
self.threadB.name = @"售票員B";
self.threadC.name = @"售票員C";
//啟動(dòng)線程
@synchronized(self) {
[self.threadA start];
[self.threadB start];
[self.threadC start];
}
}
- (void) saleTicket{
while (1) {
//鎖:必須是全局唯一的
//1.注意枷鎖的位置
//2.注意枷鎖的前提條件,多線程共享同一塊資源
//3.注意加鎖是需要代價(jià)的,需要耗費(fèi)性能的
//4.加鎖的結(jié)果:線程同步
@synchronized(self) {
//線程1
//線程2
//線程3
NSInteger count = self.totalCount;
if (count >0) {
for (NSInteger i = 0; i<1000000; i++) {
}
self.totalCount = count - 1;
//賣出去一張票
NSLog(@"%@賣出去了一張票,還剩下%zd張票", [NSThread currentThread].name,self.totalCount);
}else
{
NSLog(@"不要回公司上班了");
break;
}
}
}
}
- 專業(yè)術(shù)語(yǔ)-線程同步
- 原子和非原子屬性
- nonatomic 非原子性 即未在set方法中自動(dòng)添加鎖,可能存在多個(gè)線程對(duì)其修改時(shí)錯(cuò)誤。
- atomic 原子性 在set方法中自動(dòng)添加鎖
</br>
4. 線程間通信[即子線程做事,主線程刷新UI]
- (void)touchesBegan:(nonnull NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event{
//開(kāi)啟一條子線程來(lái)下載圖片
[NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil];
}
- (void)downloadImage{
//1.確定要下載網(wǎng)絡(luò)圖片的url地址,一個(gè)url唯一對(duì)應(yīng)著網(wǎng)絡(luò)上的一個(gè)資源
NSURL *url = [NSURL URLWithString:@"http://p6.qhimg.com/t01d2954e2799c461ab.jpg"];
//2.根據(jù)url地址下載圖片數(shù)據(jù)到本地(二進(jìn)制數(shù)據(jù) )
NSData *data = [NSData dataWithContentsOfURL:url];
//3.把下載到本地的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成圖片
UIImage *image = [UIImage imageWithData:data];
//4.回到主線程刷新UI
//4.1 第一種方式
// [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
//4.2 第二種方式
// [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
//4.3 第三種方式
[self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}
附送:計(jì)算代碼段執(zhí)行時(shí)間
//計(jì)算代碼段執(zhí)行時(shí)間的第一種方法
-(void)download1
{
//0.000018
//0.166099
//1.確定URL
NSURL *url = [NSURL URLWithString:@"http://img4.duitang.com/uploads/blog/201310/18/20131018213446_smUw4.thumb.700_0.jpeg"];
NSDate *start = [NSDate date]; //獲得當(dāng)前的時(shí)間
//2.根據(jù)url下載圖片二進(jìn)制數(shù)據(jù)到本地
NSData *imageData = [NSData dataWithContentsOfURL:url];
NSDate *end = [NSDate date]; //獲得當(dāng)前的時(shí)間
NSLog(@"%f",[end timeIntervalSinceDate:start]);
//3.轉(zhuǎn)換圖片格式
UIImage *image = [UIImage imageWithData:imageData];
//4.顯示UI
self.imageView.image = image;
}
//計(jì)算代碼段執(zhí)行時(shí)間的第二種方法
-(void)download2
{
//1.確定URL
NSURL *url = [NSURL URLWithString:@"http://img4.duitang.com/uploads/blog/201310/18/20131018213446_smUw4.thumb.700_0.jpeg"];
CFTimeInterval start = CFAbsoluteTimeGetCurrent();
//2.根據(jù)url下載圖片二進(jìn)制數(shù)據(jù)到本地
NSData *imageData = [NSData dataWithContentsOfURL:url];
CFTimeInterval end = CFAbsoluteTimeGetCurrent();
NSLog(@"end-start = %f---%f---%f",end - start,end,start);
//3.轉(zhuǎn)換圖片格式
UIImage *image = [UIImage imageWithData:imageData];
//4.顯示UI
self.imageView.image = image;
}
</br>
GCD
1. 隊(duì)列和任務(wù)
-(void)asyncConcurrent
{
//1.1自己創(chuàng)建一個(gè)隊(duì)列
/*
第一個(gè)參數(shù):C語(yǔ)言的字符串,標(biāo)簽
第二個(gè)參數(shù):隊(duì)列的類型
DISPATCH_QUEUE_CONCURRENT:并發(fā)
DISPATCH_QUEUE_SERIAL:串行
*/
//dispatch_queue_t queue = dispatch_queue_create("com.520it.download", DISPATCH_QUEUE_CONCURRENT);
//1.2獲得全局并發(fā)隊(duì)列
/*
第一個(gè)參數(shù):優(yōu)先級(jí)
第二個(gè)參數(shù):
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"---satrt----");
//2.1>封裝任務(wù)2>添加任務(wù)到隊(duì)列中
/*
第一個(gè)參數(shù):隊(duì)列
第二個(gè)參數(shù):要執(zhí)行的任務(wù)
*/
dispatch_async(queue, ^{
NSLog(@"download1----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download2----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download3----%@",[NSThread currentThread]);
});
NSLog(@"---end----");
}
</br>
2. GCD基本使用[不需要死記,理解即可。注意死鎖的情況]
* 異步函數(shù)+并發(fā)隊(duì)列:開(kāi)啟多條線程,并發(fā)執(zhí)行任務(wù)
* 異步函數(shù)+串行隊(duì)列:開(kāi)啟一條線程,串行執(zhí)行任務(wù)
* 同步函數(shù)+并發(fā)隊(duì)列:不開(kāi)線程,串行執(zhí)行任務(wù)
* 同步函數(shù)+串行隊(duì)列:不開(kāi)線程,串行執(zhí)行任務(wù)
* 異步函數(shù)+主隊(duì)列:不開(kāi)線程,在主線程中串行執(zhí)行任務(wù)
* 同步函數(shù)+主隊(duì)列:不開(kāi)線程,串行執(zhí)行任務(wù)(注意死鎖發(fā)生)
注意同步函數(shù)和異步函數(shù)在執(zhí)行順序上面的差異
//異步函數(shù)+并發(fā)隊(duì)列:會(huì)開(kāi)啟多條線程,隊(duì)列中的任務(wù)是并發(fā)執(zhí)行
-(void)asyncConcurrent
{
//1.創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.封裝任務(wù)
dispatch_async(queue, ^{
NSLog(@"download1----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download2----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download3----%@",[NSThread currentThread]);
});
NSLog(@"---end----");
}
//異步函數(shù)+串行隊(duì)列:會(huì)開(kāi)一條線程,隊(duì)列中的任務(wù)是串行執(zhí)行的
-(void)asyncSerial
{
//1.創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_SERIAL);
//2.封裝操作
dispatch_async(queue, ^{
NSLog(@"download1----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download2----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download3----%@",[NSThread currentThread]);
});
}
//同步函數(shù)+并發(fā)隊(duì)列:不會(huì)開(kāi)線程,任務(wù)是串行執(zhí)行的
-(void)syncConcurrent
{
//1.創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("com.520it.download", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"---start---");
//2.封裝任務(wù)
dispatch_sync(queue, ^{
NSLog(@"download1----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download2----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download3----%@",[NSThread currentThread]);
});
NSLog(@"---end---");
}
//同步函數(shù)+串行隊(duì)列:不會(huì)開(kāi)線程,任務(wù)是串行執(zhí)行的
-(void)syncSerial
{
//1.創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("com.520it.download", DISPATCH_QUEUE_SERIAL);
//2.封裝任務(wù)
dispatch_sync(queue, ^{
NSLog(@"download1----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download2----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download3----%@",[NSThread currentThread]);
});
}
//異步函數(shù)+主隊(duì)列:所有任務(wù)都在主線程中執(zhí)行,不會(huì)開(kāi)線程
-(void)asyncMain
{
//1.獲得主隊(duì)列
dispatch_queue_t queue = dispatch_get_main_queue();
//2.異步函數(shù)
dispatch_async(queue, ^{
NSLog(@"download1----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download2----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download3----%@",[NSThread currentThread]);
});
}
//同步函數(shù)+主隊(duì)列:死鎖
//注意:如果該方法在子線程中執(zhí)行,那么所有的任務(wù)在主線程中執(zhí)行,
-(void)syncMain
{
//1.獲得主隊(duì)列
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"start----");
//2.同步函數(shù)
//同步函數(shù):立刻馬上執(zhí)行,如果我沒(méi)有執(zhí)行完畢,那么后面的也別想執(zhí)行
//異步函數(shù):如果我沒(méi)有執(zhí)行完畢,那么后面的也可以執(zhí)行
dispatch_sync(queue, ^{
NSLog(@"download1----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download2----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download3----%@",[NSThread currentThread]);
});
NSLog(@"end---");
}
</br>
3. GCD線程間通信[子線程操作,主線程刷新UI]
//0.獲取一個(gè)全局的隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//1.先開(kāi)啟一個(gè)線程,把下載圖片的操作放在子線程中處理
dispatch_async(queue, ^{
//2.下載圖片
NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c3320b14bb3184c510fd8f9a185.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
NSLog(@"下載操作所在的線程--%@",[NSThread currentThread]);
//3.回到主線程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image;
//打印查看當(dāng)前線程
NSLog(@"刷新UI---%@",[NSThread currentThread]);
});
});
</br>
4. GCD其他常用函數(shù)
- 柵欄函數(shù)(控制任務(wù)的執(zhí)行順序)
//0.獲得全局并發(fā)隊(duì)列
//柵欄函數(shù)不能使用全局并發(fā)隊(duì)列
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
//1.異步函數(shù)
dispatch_async(queue, ^{
for (NSInteger i = 0; i<100; i++) {
NSLog(@"download1-%zd-%@",i,[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<100; i++) {
NSLog(@"download2-%zd-%@",i,[NSThread currentThread]);
}
});
//柵欄函數(shù)
dispatch_barrier_async(queue, ^{
NSLog(@"+++++++++++++++++++++++++++++");
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<100; i++) {
NSLog(@"download3-%zd-%@",i,[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<100; i++) {
NSLog(@"download4-%zd-%@",i,[NSThread currentThread]);
}
});
執(zhí)行結(jié)果是先打印download1和download2 [前后順序不一定],然后打印+++,最后再打印download3和download4 [前后順序不一定]
</br>
- 延遲執(zhí)行
[一定時(shí)間后,將執(zhí)行的操作加入到隊(duì)列中]
//1.第一種用法
/*
NSEC_PER_SEC 秒
NSEC_PER_MSEC 毫秒
NSEC_PER_USEC 微秒
*/
/*
DISPATCH_TIME_NOW 從現(xiàn)在開(kāi)始
DISPATCH_TIME_FOREVER 從此刻到永遠(yuǎn)
*/
//5秒之后
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC));
//5秒后在主線程執(zhí)行
dispatch_after(time, dispatch_get_main_queue(), ^{
//執(zhí)行操作
NSLog(@"after 5s");
});
!!!!!需要注意的是,使用dispatch_after實(shí)現(xiàn)延遲執(zhí)行某動(dòng)作,時(shí)間并不是很精確,實(shí)際上是過(guò)多久將Block追加到main Queue中,而不是執(zhí)行該動(dòng)作,理論上是:主線程 RunLoop 1/60秒檢測(cè)時(shí)間,追加的時(shí)間范圍 5s~(5+1/60)s
如果此時(shí)main queue中的任務(wù)很多,沒(méi)有執(zhí)行完畢,那么新添加的這個(gè)動(dòng)作就要繼續(xù)推遲。如果對(duì)時(shí)間的精確度沒(méi)有高要求,只是為了推遲執(zhí)行,那么使用dispatch_after還是很不錯(cuò)的。
//2.第二種用法
//執(zhí)行的c語(yǔ)言方法
dispatch_after_f(dispatch_time(DISPATCH_TIME_NOW, 3ull *NSEC_PER_SEC), dispatch_get_main_queue(), NULL, fun1);
//c函數(shù)
void fun1(){
NSLog(@"after 3s");
}
//3.第三種用法
//其實(shí)和第一種方法是一樣的,只是參數(shù)已經(jīng)幫我們填寫好了,我們只需要填寫時(shí)間和執(zhí)行block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"after 5s");
});
</br>
- 只執(zhí)行一次的代碼
[單例模式]
static XMGTool *_instance;
//本身就是線程安全的
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
</br>
- 快速迭代(開(kāi)多個(gè)線程并發(fā)完成迭代操作)
//一般的遍歷操作,我們放在一個(gè)for循環(huán)中執(zhí)行
- (void)forDemo{
//同步
for (NSInteger i = 0; i<10; i++) {
NSLog(@"%zd---%@",i,[NSThread currentThread]);
}
}
//但是如果遍歷的操作需要更快的執(zhí)行完,我們就可以使用dispatch_apply
//開(kāi)子線程和主線程一起完成遍歷任務(wù),任務(wù)的執(zhí)行時(shí)并發(fā)的。
-(void)applyDemo{
/*
第一個(gè)參數(shù):遍歷的次數(shù)
第二個(gè)參數(shù):隊(duì)列(并發(fā)隊(duì)列)
第三個(gè)參數(shù):index 索引
*/
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"%zd---%@",index,[NSThread currentThread]);
});
}
接下來(lái)我們拿一個(gè)移動(dòng)文件的例子來(lái)說(shuō)明遍歷的作用:
//使用for循環(huán)
-(void)moveFile
{
//1.拿到文件路徑
NSString *from = @"/Users/xiaomage/Desktop/from";
//2.獲得目標(biāo)文件路徑
NSString *to = @"/Users/xiaomage/Desktop/to";
//3.得到目錄下面的所有文件
NSArray *subPaths = [[NSFileManager defaultManager] subpathsAtPath:from];
//4.遍歷所有文件,然后執(zhí)行剪切操作
NSInteger count = subPaths.count;
for (NSInteger i = 0; i< count; i++) {
//4.1 拼接文件的全路徑
// NSString *fullPath = [from stringByAppendingString:subPaths[i]];
NSString *fullPath = [from stringByAppendingPathComponent:subPaths[i]];
NSString *toFullPath = [to stringByAppendingPathComponent:subPaths[i]];
NSLog(@"%@",fullPath);
//4.2 執(zhí)行剪切操作
/*
第一個(gè)參數(shù):要剪切的文件在哪里
第二個(gè)參數(shù):文件應(yīng)該被存到哪個(gè)位置
*/
[[NSFileManager defaultManager]moveItemAtPath:fullPath toPath:toFullPath error:nil];
NSLog(@"%@---%@--%@",fullPath,toFullPath,[NSThread currentThread]);
}
}
-(void)moveFileWithGCD
{
//1.拿到文件路徑
NSString *from = @"/Users/xiaomage/Desktop/from";
//2.獲得目標(biāo)文件路徑
NSString *to = @"/Users/xiaomage/Desktop/to";
//3.得到目錄下面的所有文件
NSArray *subPaths = [[NSFileManager defaultManager] subpathsAtPath:from];
NSLog(@"%@",subPaths);
//4.遍歷所有文件,然后執(zhí)行剪切操作
NSInteger count = subPaths.count;
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
//4.1 拼接文件的全路徑
// NSString *fullPath = [from stringByAppendingString:subPaths[i]];
//在拼接的時(shí)候會(huì)自動(dòng)添加/
NSString *fullPath = [from stringByAppendingPathComponent:subPaths[i]];
NSString *toFullPath = [to stringByAppendingPathComponent:subPaths[i]];
NSLog(@"%@",fullPath);
//4.2 執(zhí)行剪切操作
/*
第一個(gè)參數(shù):要剪切的文件在哪里
第二個(gè)參數(shù):文件應(yīng)該被存到哪個(gè)位置
*/
[[NSFileManager defaultManager]moveItemAtPath:fullPath toPath:toFullPath error:nil];
NSLog(@"%@---%@--%@",fullPath,toFullPath,[NSThread currentThread]);
});
}
需要注意的是,并不是count為多少就開(kāi)啟多少個(gè)線程,系統(tǒng)會(huì)自己判斷需要開(kāi)啟多少個(gè)線程來(lái)完成此次遍歷。換句話說(shuō),如果遍歷時(shí)已經(jīng)開(kāi)啟了兩條異步線程,這時(shí)第三條數(shù)據(jù)需要異步操作,系統(tǒng)會(huì)判斷之前的線程有沒(méi)有空閑的,如果有空閑的線程,那么不會(huì)創(chuàng)建新的線程,而是用空閑線程來(lái)完成任務(wù)。
</br>
- 隊(duì)列組
如果某個(gè)界面上有兩個(gè)接口請(qǐng)求,我們需要在兩個(gè)接口都返回的情況下刷新UI,那么我們可以使用隊(duì)列組來(lái)實(shí)現(xiàn)。
//1.創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//2.創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
//3.異步函數(shù)
/*
1)封裝任務(wù)
2)把任務(wù)添加到隊(duì)列中
3)dispatch_group_notify會(huì)監(jiān)聽(tīng)任務(wù)的執(zhí)行情況,通知group
*/
dispatch_group_async(group, queue, ^{
NSLog(@"1----%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"2----%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"3----%@",[NSThread currentThread]);
});
//攔截通知,當(dāng)隊(duì)列組中所有的任務(wù)都執(zhí)行完畢的時(shí)候回進(jìn)入到下面的方法
dispatch_group_notify(group, queue, ^{
NSLog(@"-------dispatch_group_notify-------");
});
之前我們創(chuàng)建一個(gè)異步線程操作是使用方法dispatch_async,而這里是使用dispatch_group_async
第二種實(shí)現(xiàn)方法:
//1.創(chuàng)建隊(duì)列
dispatch_queue_t queue =dispatch_get_global_queue(0, 0);
//2.創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
//3.在該方法后面的異步任務(wù)會(huì)被納入到隊(duì)列組的監(jiān)聽(tīng)范圍,進(jìn)入群組
//dispatch_group_enter|dispatch_group_leave 必須要配對(duì)使用
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"1----%@",[NSThread currentThread]);
//離開(kāi)群組
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"2----%@",[NSThread currentThread]);
//離開(kāi)群組
dispatch_group_leave(group);
});
//攔截通知
//問(wèn)題?該方法是阻塞的嗎? 內(nèi)部本身是異步的,所以不會(huì)
// dispatch_group_notify(group, queue, ^{
// NSLog(@"-------dispatch_group_notify-------");
// });
//等待.死等. 直到隊(duì)列組中所有的任務(wù)都執(zhí)行完畢之后才能執(zhí)行
//阻塞的
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"----end----");
</br>
NSOperation
-
相關(guān)概念
- NSOperation是對(duì)GCD的包裝
- 兩個(gè)核心概念
[隊(duì)列]+[操作]
-
基本使用
- NSOperation本身是抽象類,我們使用的是他的子類
- 三個(gè)子類分別是:NSBlockOperation, NSInvocationOperation以及自定義繼承自NSOperation的類
- NSOperation單獨(dú)使用無(wú)法實(shí)現(xiàn)多線程,需要和NSOperationQueue結(jié)合使用才能實(shí)現(xiàn)。
相關(guān)代碼
// 01 NSInvocationOperation
//1.封裝操作
/*
第一個(gè)參數(shù):目標(biāo)對(duì)象
第二個(gè)參數(shù):該操作要調(diào)用的方法,最多接受一個(gè)參數(shù)
第三個(gè)參數(shù):調(diào)用方法傳遞的參數(shù),如果方法不接受參數(shù),那么該值傳nil
*/
//必須添加隊(duì)列才會(huì)生成新線程
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self selector:@selector(run) object:nil];
//2.啟動(dòng)操作
[operation start];
-------------------------------------------------
// 02 NSBlockOperation
//1.封裝操作
/*
NSBlockOperation提供了一個(gè)類方法,在該類方法中封裝操作
*/
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
//在主線程中執(zhí)行
NSLog(@"---download1--%@",[NSThread currentThread]);
}];
//2.追加操作,追加的操作在子線程中執(zhí)行
//追加任務(wù)
//注意:如果一個(gè)操作中的任務(wù)數(shù)量大于1,那么會(huì)開(kāi)子線程并發(fā)執(zhí)行任務(wù)
//注意:不一定是子線程,有可能是主線程
[operation addExecutionBlock:^{
NSLog(@"---download2--%@",[NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"---download3--%@",[NSThread currentThread]);
}];
//3.啟動(dòng)執(zhí)行操作
[operation start];
----------------------------------------------
// 03 自定義NSOperation
//如何封裝操作?
//自定義的NSOperation,通過(guò)重寫內(nèi)部的main方法實(shí)現(xiàn)封裝操作
-(void)main
{
NSLog(@"--main--%@",[NSThread currentThread]);
}
//如何使用?
//1.實(shí)例化一個(gè)自定義操作對(duì)象
XMGOperation *op = [[XMGOperation alloc]init];
//2.執(zhí)行操作
[op start];
</br>
-
NSOperationQueue基本使用
- NSOperationQueue中的兩種隊(duì)列
- 主隊(duì)列 通過(guò)mainQueue獲得,凡是放到主隊(duì)列中的任務(wù)都是將在主線程執(zhí)行
- 非主隊(duì)列 直接alloc init出來(lái)的隊(duì)列。非主隊(duì)列同時(shí)具備了并發(fā)和串行的功能,通過(guò)設(shè)置最大并發(fā)數(shù)屬性來(lái)控制任務(wù)是并發(fā)任務(wù)還是串行任務(wù)。
- NSOperationQueue中的兩種隊(duì)列
相關(guān)代碼
//自定義NSOperation
-(void)customOperation
{
//1.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2.封裝操作
//好處:1.信息隱蔽
//2.代碼復(fù)用
XMGOperation *op1 = [[XMGOperation alloc]init];
XMGOperation *op2 = [[XMGOperation alloc]init];
//3.添加操作到隊(duì)列中
[queue addOperation:op1];
[queue addOperation:op2];
}
//NSBlockOperation
- (void)block
{
//1.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2.封裝操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1----%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2----%@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
NSLog(@"3----%@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
NSLog(@"4----%@",[NSThread currentThread]);
}];
//3.添加操作到隊(duì)列中
[queue addOperation:op1];
[queue addOperation:op2];
//補(bǔ)充:簡(jiǎn)便方法
[queue addOperationWithBlock:^{
NSLog(@"5----%@",[NSThread currentThread]);
}];
}
//NSInvocationOperation
- (void)invocation
{
/*
GCD中的隊(duì)列:
串行隊(duì)列:自己創(chuàng)建的,主隊(duì)列
并發(fā)隊(duì)列:自己創(chuàng)建的,全局并發(fā)隊(duì)列
NSOperationQueue
主隊(duì)列:[NSOperationQueue mainqueue];凡事放在主隊(duì)列中的操作都在主線程中執(zhí)行
非主隊(duì)列:[[NSOperationQueue alloc]init],并發(fā)和串行,默認(rèn)是并發(fā)執(zhí)行的
*/
//1.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2.封裝操作
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download2) object:nil];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download3) object:nil];
//3.把封裝好的操作添加到隊(duì)列中
//不需要手動(dòng)調(diào)用[op1 start]
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
</br>
- NSOperation其他用法
- 設(shè)置最大并發(fā)數(shù)[控制任務(wù)并發(fā)和串行]
//1.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2.設(shè)置最大并發(fā)數(shù)量 maxConcurrentOperationCount
//同一時(shí)間最多有多少個(gè)任務(wù)可以執(zhí)行(實(shí)際開(kāi)起的線程總數(shù)大于maxConcurrentOperationCount)
//串行執(zhí)行任務(wù)!=只開(kāi)一條線程 (線程同步)
// maxConcurrentOperationCount >1 那么就是并發(fā)隊(duì)列
// maxConcurrentOperationCount == 1 那就是串行隊(duì)列
// maxConcurrentOperationCount == 0 不會(huì)執(zhí)行任務(wù)
// maxConcurrentOperationCount == -1 特殊意義 最大值 表示不受限制
queue.maxConcurrentOperationCount = 2;
- 暫停和恢復(fù)以及取消
//設(shè)置暫停和恢復(fù)
//suspended設(shè)置為YES表示暫停,suspended設(shè)置為NO表示恢復(fù)
//暫停表示不繼續(xù)執(zhí)行隊(duì)列中的下一個(gè)任務(wù),暫停操作是可以恢復(fù)的
if (self.queue.isSuspended) {
self.queue.suspended = NO;
}else
{
self.queue.suspended = YES;
}
//取消隊(duì)列里面的所有操作
//取消之后,當(dāng)前正在執(zhí)行的操作的下一個(gè)操作將不再執(zhí)行,而且永遠(yuǎn)都不在執(zhí)行,就像后面的所有任務(wù)都從隊(duì)列里面移除了一樣
//取消操作是不可以恢復(fù)的
[self.queue cancelAllOperations];
---------自定義NSOperation取消操作--------------------------
//自定義NSOperation子類需要重寫main方法,用于執(zhí)行線程中的操作
-(void)main
{
//耗時(shí)操作1
for (int i = 0; i<1000; i++) {
NSLog(@"任務(wù)1-%d--%@",i,[NSThread currentThread]);
}
NSLog(@"+++++++++++++++++++++++++++++++++");
//蘋果官方建議,每當(dāng)執(zhí)行完一次耗時(shí)操作之后,就查看一下當(dāng)前隊(duì)列是否為取消狀態(tài),如果是,那么就直接退出
//好處是可以提高程序的性能
if (self.isCancelled) {
return;
}
//耗時(shí)操作2
for (int i = 0; i<1000; i++) {
NSLog(@"任務(wù)1-%d--%@",i,[NSThread currentThread]);
}
NSLog(@"+++++++++++++++++++++++++++++++++");
}
- 下載多張圖片合成綜合案例(設(shè)置操作依賴)
/*
1.下載圖片1
2.下載圖片2
3.合并圖片
*/
-(void)comBie
{
//1.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
__block UIImage *image1;
__block UIImage *image2;
//2 封裝操作,下載圖片1
NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://s15.sinaimg.cn/bmiddle/4c0b78455061c1b7f1d0e"];
NSData *imageData = [NSData dataWithContentsOfURL:url];
image1 = [UIImage imageWithData:imageData];
NSLog(@"download---%@",[NSThread currentThread]);
}];
//3 封裝操作,下載圖片2
NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://www.027art.com/feizhuliu/UploadFiles_6650/201109/2011091718442835.jpg"];
NSData *imageData = [NSData dataWithContentsOfURL:url];
image2 = [UIImage imageWithData:imageData];
NSLog(@"download---%@",[NSThread currentThread]);
}];
//4.封裝合并圖片的操作
NSBlockOperation *combie = [NSBlockOperation blockOperationWithBlock:^{
//4.1 開(kāi)上下文
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
//4.2 畫圖1
[image1 drawInRect:CGRectMake(0, 0, 100, 200)];
//4.3 畫圖2
[image2 drawInRect:CGRectMake(100, 0, 100, 200)];
//4.4 根據(jù)上下文得到圖片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
//4.5 關(guān)閉上下文
UIGraphicsEndImageContext();
//7.更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
NSLog(@"UI----%@",[NSThread currentThread]);
}];
}];
//5.設(shè)置依賴關(guān)系
//依賴的作用是:線程會(huì)在依賴執(zhí)行完成后,再執(zhí)行
[combie addDependency:download1];
[combie addDependency:download2];
//6.添加操作到隊(duì)列中
[queue addOperation:download2];
[queue addOperation:download1];
[queue addOperation:combie];
}
填坑...