1、進程與線程
1.1 進程
進程是指在系統(tǒng)中正在運行的一個應(yīng)用程序
每個進程之間是獨立的,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi)
1.2 線程
1個進程要想執(zhí)行任務(wù),必須得有線程(每1個進程至少要有1條線程,稱為主線程)
一個進程(程序)的所有任務(wù)都在線程中執(zhí)行
1.3 進程和線程的比較
1.線程是CPU調(diào)用(執(zhí)行任務(wù))的最小單位。
2.進程是CPU分配資源的最小單位。
3.一個進程中至少要有一個線程。
4.同一個進程內(nèi)的線程共享進程的資源。
1.4 多線程原理
同一時間,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行),多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間調(diào)度(切換),如果CPU調(diào)度線程的時間足夠快,就造成了多線程并發(fā)執(zhí)行的假象。
那么如果線程非常非常多,會發(fā)生什么情況?
CPU會在N多線程之間調(diào)度,CPU會累死,消耗大量的CPU資源,同時每條線程被調(diào)度執(zhí)行的頻次也會會降低(線程的執(zhí)行效率降低)。
因此我們一般只開3-5條線程。
1.5 多線程優(yōu)缺點
多線程的優(yōu)點
能適當(dāng)提高程序的執(zhí)行效率
能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
多線程的缺點
創(chuàng)建線程是有開銷的,iOS下主要成本包括:內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1KB)、棧空間(子線程512KB、主線程1MB,也可以使用-setStackSize:設(shè)置,但必須是4K的倍數(shù),而且最小是16K),創(chuàng)建線程大約需要90毫秒的創(chuàng)建時間
如果開啟大量的線程,會降低程序的性能,線程越多,CPU在調(diào)度線程上的開銷就越大。
程序設(shè)計更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享等問題。
1.6 多線程的應(yīng)用
主線程的主要作用:
顯示\刷新UI界面
處理UI事件(比如點擊事件、滾動事件、拖拽事件等)
主線程的使用注意:
別將比較耗時的操作放到主線程中
耗時操作會卡住主線程,嚴(yán)重影響UI的流暢度,給用戶一種“卡”的壞體驗
將耗時操作放在子線程中執(zhí)行,提高程序的執(zhí)行效率
1.7多線程實現(xiàn)方案

2、pthread
POSIX線程(POSIX threads),簡稱Pthreads,是線程的POSIX標(biāo)準(zhǔn)。該標(biāo)準(zhǔn)定義了創(chuàng)建和操縱線程的一整套API。在類Unix操作系統(tǒng)(Unix、Linux、Mac OS X等)中,都使用Pthreads作為操作系統(tǒng)的線程。
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//創(chuàng)建線程
pthread_t thread;
/*
第一個參數(shù)pthread_t *restrict:線程對象
第二個參數(shù)const pthread_attr_t *restrict:線程屬性
第三個參數(shù)void *(*)(void *) :指向函數(shù)的指針
第四個參數(shù)void *restrict:函數(shù)的參數(shù)
*/
pthread_create(&thread, NULL,run ,NULL);
}
//void *(*)(void *)
void *run(void *param)
{
for (NSInteger i =0 ; i<10000; i++) {
NSLog(@"%zd--%@-",i,[NSThread currentThread]);
}
return NULL;
}
3、NSTread
3.1 NSTread創(chuàng)建線程方法
// 方法一:創(chuàng)建線程,需要自己開啟線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// 開啟線程
[thread start];
// 方法二:創(chuàng)建線程后自動啟動線程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
// 方法三:隱式創(chuàng)建并啟動線程
[self performSelectorInBackground:@selector(run) withObject:nil];
//后面兩種方法都不用我們開啟線程,相對方便快捷,
//但是沒有辦法拿到子線程對象,沒有辦法對子線程進行更詳細的設(shè)置,
//例如線程名字和優(yōu)先級等。
3.2 NSThread的屬性
// 獲取當(dāng)前線程
+ (NSThread *)currentThread;
// 創(chuàng)建啟動線程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
// 判斷是否是多線程
+ (BOOL)isMultiThreaded;
// 線程休眠 NSDate 休眠到什么時候
+ (void)sleepUntilDate:(NSDate *)date;
// 線程休眠時間
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 結(jié)束/退出當(dāng)前線程
+ (void)exit;
// 獲取當(dāng)前線程優(yōu)先級
+ (double)threadPriority;
// 設(shè)置線程優(yōu)先級 默認(rèn)為0.5 取值范圍為0.0 - 1.0
// 1.0優(yōu)先級最高
// 設(shè)置優(yōu)先級
+ (BOOL)setThreadPriority:(double)p;
// 獲取指定線程的優(yōu)先級
- (double)threadPriority NS_AVAILABLE(10_6, 4_0);
- (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0);
// 設(shè)置線程的名字
- (void)setName:(NSString *)n NS_AVAILABLE(10_5, 2_0);
- (NSString *)name NS_AVAILABLE(10_5, 2_0);
// 判斷指定的線程是否是 主線程
- (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0);
// 判斷當(dāng)前線程是否是主線程
+ (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main
// 獲取主線程
+ (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);
- (id)init NS_AVAILABLE(10_5, 2_0); // designated initializer
// 創(chuàng)建線程
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2_0);
// 指定線程是否在執(zhí)行
- (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0);
// 線程是否完成
- (BOOL)isFinished NS_AVAILABLE(10_5, 2_0);
// 線程是否被取消 (是否給當(dāng)前線程發(fā)過取消信號)
- (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0);
// 發(fā)送線程取消信號的 最終線程是否結(jié)束 由 線程本身決定
- (void)cancel NS_AVAILABLE(10_5, 2_0);
// 啟動線程
- (void)start NS_AVAILABLE(10_5, 2_0);
// 線程主函數(shù) 在線程中執(zhí)行的函數(shù) 都要在-main函數(shù)中調(diào)用,自定義線程中重寫-main方法
- (void)main NS_AVAILABLE(10_5, 2_0); // thread body metho
3.3 NSThread線程的狀態(tài)

啟動線程
- (void)start;
// 進入就緒狀態(tài) -> 運行狀態(tài)。當(dāng)線程任務(wù)執(zhí)行完畢,自動進入死亡狀態(tài)
阻塞(暫停)線程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 進入阻塞狀態(tài)
強制停止線程
+ (void)exit;
// 進入死亡狀態(tài)
3.4 NSThread多線程安全隱患
多線程安全隱患的原因:當(dāng)一塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源,比如多個線程訪問同一個對象、同一個變量、同一個文件。
那么當(dāng)多個線程訪問同一塊資源時,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題。
安全隱患解決方法:互斥鎖
當(dāng)線程A訪問數(shù)據(jù)并對數(shù)據(jù)進行操作的時候,數(shù)據(jù)被加上一把鎖,這個時候其他線程都無法訪問數(shù)據(jù),知道線程A結(jié)束返回數(shù)據(jù),線程B此時在訪問數(shù)據(jù)并修改,就不會造成數(shù)據(jù)錯亂了。
下面我們來看一下互斥鎖的使用:
互斥鎖使用格式
@synchronized(鎖對象) {
// 需要鎖定的代碼
}
互斥鎖的使用前提:多條線程搶奪同一塊資源時
注:鎖定一份代碼只用一把鎖,用多把鎖是無效的
互斥鎖的優(yōu)缺點
優(yōu)點:能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
缺點:需要消耗大量的CPU資源
3.5 NSThread線程之間的通信
(1)什么叫做線程間通信
在1個進程中,線程往往不是孤立存在的,多個線程之間需要經(jīng)常進行通信,例如我們在子線程完成下載圖片后,回到主線程刷新UI顯示圖片
(2)線程間通信的體現(xiàn)
一個線程傳遞數(shù)據(jù)給另一個線程
在一個線程中執(zhí)行完特定任務(wù)后,轉(zhuǎn)到另一個線程繼續(xù)執(zhí)行任務(wù)
線程間通信常用的方法:
// 返回主線程
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// 返回指定線程
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
4. GCD的使用
GCD的全稱是Grand Central Dispatch,是純C語言,提供了非常多強大的函數(shù)
GCD的優(yōu)勢
GCD是蘋果公司為多核的并行運算提出的解決方案
GCD會自動利用更多的CPU內(nèi)核(比如雙核、四核)
GCD會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼
4.1 任務(wù)和隊列
GCD中有2個核心概念:任務(wù)和隊列
(1) 任務(wù)
任務(wù):執(zhí)行什么操作,任務(wù)有兩種執(zhí)行方式: 同步函數(shù) 和 異步函數(shù),他們之間的區(qū)別是:
同步函數(shù):只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力,任務(wù)立刻馬上執(zhí)行,會阻塞當(dāng)前線程并等待 Block中的任務(wù)執(zhí)行完畢,然后當(dāng)前線程才會繼續(xù)往下運行
異步函數(shù):可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力,但不一定會開新線程,當(dāng)前線程會直接往下執(zhí)行,不會阻塞當(dāng)前線程
(2) 隊列
隊列:用來存放任務(wù),分為串行隊列 和 并行隊列
串行隊列(Serial Dispatch Queue)
讓任務(wù)一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))
并發(fā)隊列(Concurrent Dispatch Queue)
可以讓多個任務(wù)并發(fā)(同時)執(zhí)行(自動開啟多個線程同時執(zhí)行任務(wù))
并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
(3)GCD的使用就2個步驟
a. 定制任務(wù) , 確定想做的事情
b. 將任務(wù)添加到隊列中
GCD會自動將隊列中的任務(wù)取出,放到對應(yīng)的線程中執(zhí)行
任務(wù)的取出遵循隊列的FIFO原則:先進先出,后進后出
4.2 GCD的創(chuàng)建
(1)隊列的創(chuàng)建
// 第一個參數(shù)const char *label : C語言字符串,用來標(biāo)識
// 第二個參數(shù)dispatch_queue_attr_t attr : 隊列的類型
// 并發(fā)隊列:DISPATCH_QUEUE_CONCURRENT
// 串行隊列:DISPATCH_QUEUE_SERIAL 或者 NULL
dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
//創(chuàng)建并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_CONCURRENT);
//創(chuàng)建串行隊列
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);
//GCD默認(rèn)已經(jīng)提供了全局并發(fā)隊列,供整個應(yīng)用使用,可以無需手動創(chuàng)建
dispatch_queue_t quque1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/**
第一個參數(shù):優(yōu)先級 也可直接填后面的數(shù)字
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(rèn)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺
第二個參數(shù): 預(yù)留參數(shù) 0
*/
//獲得主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
(2)任務(wù)的執(zhí)行
隊列在queue中,任務(wù)在block塊中
/*
第一個參數(shù):隊列
第二個參數(shù):block,在里面封裝任務(wù)
*/
//開啟同步函數(shù) 同步函數(shù):要求立刻馬上開始執(zhí)行
dispatch_sync(queue, ^{
});
//開啟異步函數(shù) 異步函數(shù) :等主線程執(zhí)行完畢之后,回過頭開線程執(zhí)行任務(wù)
dispatch_async(queue, ^{
});
(3)任務(wù)和隊列的組合
異步函數(shù)+并發(fā)隊列:會開啟新的線程,并發(fā)執(zhí)行
異步函數(shù)+串行隊列:會開啟一條線程,任務(wù)串行執(zhí)行
同步函數(shù)+并發(fā)隊列:不會開線程,任務(wù)串行執(zhí)行
同步函數(shù)+串行隊列:不會開線程,任務(wù)串行執(zhí)行
異步函數(shù)+主隊列: 不會開線程,任務(wù)串行執(zhí)行
注:GCD中開多少條線程是由系統(tǒng)根據(jù)CUP繁忙程度決定的,如果任務(wù)很多,GCD會開啟適當(dāng)?shù)淖泳€程,并不會讓所有任務(wù)同時執(zhí)行。
(4)使用主隊列(跟主線程相關(guān)聯(lián)的隊列)
主隊列是GCD自帶的一種特殊的串行隊列,放在主隊列中的任務(wù),都會放到主線程中執(zhí)行
//1.獲得主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
//2.異步函數(shù)
dispatch_async(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
同步函數(shù)+主隊列
//1.獲得主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
//2.同步函數(shù)
dispatch_sync(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
注:因為這個方法在主線程中,給主線程中添加任務(wù),而同步函數(shù)要求立刻馬上執(zhí)行,因此就會相互等待而發(fā)生死鎖。將這個方法放入子線程中,則不會發(fā)生死鎖,任務(wù)串行執(zhí)行。

4.3 線程間通信
GCD線程間的通信非常簡單,使用同步或異步函數(shù),傳入主隊列即可。
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
// 獲得圖片URL
NSURL *url = [NSURL URLWithString:@"http://upload-images.jianshu.io/upload_images/2301429-d5cc0a007447e469.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"];
// 將圖片URL下載為二進制文件
NSData *data = [NSData dataWithContentsOfURL:url];
// 將二進制文件轉(zhuǎn)化為image
UIImage *image = [UIImage imageWithData:data];
NSLog(@"%@",[NSThread currentThread]);
// 返回主線程 這里用同步函數(shù)不會發(fā)生死鎖,因為這個方法在子線程中被調(diào)用。
// 也可以使用異步函數(shù)
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"%@",[NSThread currentThread]);
});
});
4.4 GCD其他常用函數(shù)
(1)柵欄函數(shù)(控制任務(wù)的執(zhí)行順序)
柵欄函數(shù)可以控制任務(wù)執(zhí)行的順序,柵欄函數(shù)之前的執(zhí)行完畢之后,執(zhí)行柵欄函數(shù),然后在執(zhí)行柵欄函數(shù)之后的
dispatch_barrier_async(queue, ^{
NSLog(@"--dispatch_barrier_async-");
});
(2)延遲執(zhí)行(延遲·控制在哪個線程執(zhí)行)
/*
第一個參數(shù):延遲時間
第二個參數(shù):要執(zhí)行的代碼
如果想讓延遲的代碼在子線程中執(zhí)行,也可以更改在哪個隊列中執(zhí)行 dispatch_get_main_queue() -> dispatch_get_global_queue(0, 0)
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"---%@",[NSThread currentThread]);
});
擴展:延遲執(zhí)行的其他方法:
// 2s中之后調(diào)用run方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// repeats:YES 是否重復(fù)
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
(3)一次性代碼
-(void)once
{
//整個程序運行過程中只會執(zhí)行一次
//onceToken用來記錄該部分的代碼是否被執(zhí)行過
//用于單例中
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"-----");
});
}
(4)快速迭代(開多個線程并發(fā)完成迭代操作)
/*
第一個參數(shù):迭代的次數(shù)
第二個參數(shù):在哪個隊列中執(zhí)行
第三個參數(shù):block要執(zhí)行的任務(wù)
*/
dispatch_apply(10, queue, ^(size_t index) {
});
快速迭代:開啟多條線程,并發(fā)執(zhí)行,相比于for循環(huán)在耗時操作中極大的提高效率和速度
(5)隊列組(同柵欄函數(shù))
// 創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
// 創(chuàng)建并行隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 執(zhí)行隊列組任務(wù)
dispatch_group_async(group, queue, ^{
});
//隊列組中的任務(wù)執(zhí)行完畢之后,執(zhí)行該函數(shù)
dispatch_group_notify(group, queue, ^{
});
5. NSOperation的使用
NSOperation 是蘋果公司對 GCD 的封裝,完全面向?qū)ο螅⒈菺CD多了一些更簡單實用的功能,所以使用起來更加方便易于理解。NSOperation 和NSOperationQueue 分別對應(yīng) GCD 的 任務(wù) 和 隊列。
NSOperation和NSOperationQueue實現(xiàn)多線程的具體步驟
1.將需要執(zhí)行的操作封裝到一個NSOperation對象中
2.將NSOperation對象添加到NSOperationQueue中
系統(tǒng)會自動將NSOperationQueue中的NSOperation取出來,并將取出的NSOperation封裝的操作放到一條新線程中執(zhí)行
5.1 NSOperation的創(chuàng)建
NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類
使用NSOperation子類的方式有3種
(1)NSInvocationOperation
/*
第一個參數(shù):目標(biāo)對象
第二個參數(shù):選擇器,要調(diào)用的方法
第三個參數(shù):方法要傳遞的參數(shù)
*/
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
//啟動操作
[op start];
(2)NSBlockOperation(最常用)
//1.封裝操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
//要執(zhí)行的操作,在主線程中執(zhí)行
NSLog(@"1------%@",[NSThread currentThread]);
}];
//2.追加操作,追加的操作在子線程中執(zhí)行,可以追加多條操作
[op addExecutionBlock:^{
NSLog(@"---download2--%@",[NSThread currentThread]);
}];
[op start];
(3)自定義子類繼承NSOperation,實現(xiàn)內(nèi)部相應(yīng)的方法
// 重寫自定義類的main方法實現(xiàn)封裝操作
//自定義類封裝性高,復(fù)用性高。
-(void)main
{
// 要執(zhí)行的操作
}
// 實例化一個自定義對象,并執(zhí)行操作
CLOperation *op = [[CLOperation alloc]init];
[op start];
5.2 NSOperationQueue的使用
NSOperation中的兩種隊列
主隊列:通過mainQueue獲得,凡是放到主隊列中的任務(wù)都將在主線程執(zhí)行
非主隊列:直接alloc init出來的隊列。非主隊列同時具備了并發(fā)和串行的功能,通過設(shè)置最大并發(fā)數(shù)屬性來控制任務(wù)是并發(fā)執(zhí)行還是串行執(zhí)行
NSOperationQueue的作用
NSOperation可以調(diào)用start方法來執(zhí)行任務(wù),但默認(rèn)是同步執(zhí)行的
如果將NSOperation添加到NSOperationQueue(操作隊列)中,系統(tǒng)會自動異步執(zhí)行NSOperation中的操作
添加操作到NSOperationQueue中
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
注:將操作添加到NSOperationQueue中,就會自動啟動,不需要再自己啟動了addOperation 內(nèi)部調(diào)用 start方法start方法 內(nèi)部調(diào)用 main方法
5.3 NSOperation和NSOperationQueue結(jié)合使用創(chuàng)建多線程
//注:這里使用NSBlockOperation示例,其他兩種方法一樣
// 1. 創(chuàng)建非主隊列 同時具備并發(fā)和串行的功能,默認(rèn)是并發(fā)隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//NSBlockOperation 不論封裝操作還是追加操作都是異步并發(fā)執(zhí)行
// 2. 封裝操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download1 -- %@",[NSThread currentThread]);
}];
// 3. 將封裝操作加入隊列
[queue addOperation:op1];
// 也可以不獲取封裝操作對象 直接添加操作到隊列中
[queue addOperationWithBlock:^{
// 操作
}];
//獲取主隊列
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
//將操作加入主隊列
[mainQueue addOperationWithBlock:^{
// 操作
}];
5.4 NSOperation和NSOperationQueue的常用屬性和方法
NSOperation
(1)NSOperation的依賴
- (void)addDependency:(NSOperation *)op;
// 操作op1依賴op5,即op1必須等op5執(zhí)行完畢之后才會執(zhí)行
// 添加操作依賴,注意不能循環(huán)依賴,如果循環(huán)依賴會造成兩個任務(wù)都不會執(zhí)行
// 也可以夸隊列依賴,依賴別的隊列的操作
[op1 addDependency:op5];
(2)NSOperation操作監(jiān)聽
void (^completionBlock)(void)
// 監(jiān)聽操作的完成
// 當(dāng)op1線程完成之后,立刻就會執(zhí)行block塊中的代碼
// block中的代碼與op1不一定在一個線程中執(zhí)行,但是一定在子線程中執(zhí)行
op1.completionBlock = ^{
NSLog(@"op1已經(jīng)完成了---%@",[NSThread currentThread]);
};
NSOperationQueue
(1)maxConcurrentOperationCount
//1.創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
/*
默認(rèn)是并發(fā)隊列,如果最大并發(fā)數(shù)>1,并發(fā)
如果最大并發(fā)數(shù)==1,串行隊列
系統(tǒng)的默認(rèn)是最大并發(fā)數(shù)-1 ,表示不限制
設(shè)置成0則不會執(zhí)行任何操作
*/
queue.maxConcurrentOperationCount = 1;
(2)suspended
//當(dāng)值為YES的時候暫停,為NO的時候是恢復(fù)
queue.suspended = YES;
(3)-(void)cancelAllOperations;
//取消所有的任務(wù),不再執(zhí)行,不可逆
[queue cancelAllOperations];
注意:暫停和取消只能暫?;蛉∠幱诘却隣顟B(tài)的任務(wù),不能暫停或取消正在執(zhí)行中的任務(wù),必須等正在執(zhí)行的任務(wù)執(zhí)行完畢之后才會暫停。
如果想要暫?;蛘呷∠趫?zhí)行的任務(wù),可以在每個任務(wù)之間即每當(dāng)執(zhí)行完一段耗時操作之后,判斷是否任務(wù)是否被取消或者暫停。如果想要精確的控制,則需要將判斷代碼放在任務(wù)之中,但是不建議這么做,頻繁的判斷會消耗太多時間
5.5 NSOperation和NSOperationQueue的一些其他屬性和方法
NSOperation
// 開啟線程
- (void)start;
- (void)main;
// 判斷線程是否被取消
@property (readonly, getter=isCancelled) BOOL cancelled;
// 取消當(dāng)前線程
- (void)cancel;
//NSOperation任務(wù)是否在運行
@property (readonly, getter=isExecuting) BOOL executing;
//NSOperation任務(wù)是否已結(jié)束
@property (readonly, getter=isFinished) BOOL finished;
// 添加依賴
- (void)addDependency:(NSOperation *)op;
// 移除依賴
- (void)removeDependency:(NSOperation *)op;
// 優(yōu)先級
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
// 操作監(jiān)聽
@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);
// 阻塞當(dāng)前線程,直到該NSOperation結(jié)束??捎糜诰€程執(zhí)行順序的同步
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);
// 獲取線程的優(yōu)先級
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);
// 線程名稱
@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);
@end
NSOperationQueue
// 獲取隊列中的操作
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
// 隊列中的操作數(shù)
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);
// 最大并發(fā)數(shù),同一時間最多只能執(zhí)行三個操作
@property NSInteger maxConcurrentOperationCount;
// 暫停 YES:暫停 NO:繼續(xù)
@property (getter=isSuspended) BOOL suspended;
// 取消所有操作
- (void)cancelAllOperations;
// 阻塞當(dāng)前線程直到此隊列中的所有任務(wù)執(zhí)行完畢
- (void)waitUntilAllOperationsAreFinished;
5.6 NSOperation線程之間的通信
NSOperation線程之間的通信方法
// 回到主線程刷新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
注:子線程執(zhí)行完操作之后就會立即釋放,即使我們使用強引用引用子線程使子線程不被釋放,也不能給子線程再次添加操作,或者再次開啟。
6、GCD 與 NSOperation 的對比
對比它們可以從下面幾個角度來說:
(1)首先要明確一點,NSOperationQueue 是基于 GCD 的更高層的封裝(從 OS X 10.10 開始可以通過設(shè)置 underlyingQueue 來把 operation 放到已有的 dispatch queue 中。)
(2)從易用性角度,GCD 由于采用 C 風(fēng)格的 API,在調(diào)用上比使用面向?qū)ο箫L(fēng)格的 NSOperation 要簡單一些。但是NSOperation有更多的API可以直接調(diào)用
(3)從對任務(wù)的控制性來說,NSOperation 顯著得好于 GCD。
和 GCD 相比支持了 Cancel 操作,支持任務(wù)之間的依賴關(guān)系,支持同一個隊列中任務(wù)的優(yōu)先級設(shè)置,同時還可以通過 KVO 來監(jiān)控任務(wù)的執(zhí)行情況,可以監(jiān)測operation是否正在執(zhí)行(isExecuted)、是否結(jié)束(isFinished),是否取消(isCanceld)。(這些通過 GCD 也可以實現(xiàn),不過需要很多代碼,使用 NSOperation 顯得方便了很多。)
(4)從第三方庫的角度,最新版本的SDWebImage,定義了線程宏,全局使用
#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
#endif
拜讀大神的文章,受益頗多
參考文獻: