ios 多線程,線程生命為周期,多線程的使用,NSThread,GCD,NSOperation

前言:

最近想回顧一下多線程問題,看到一篇文章寫的非常詳細,為了便于以后查找以及加深印象,就照著原文摘錄了下文,原著勿怪:

原文地址:http://www.cocoachina.com/ios/20170707/19769.html

一,多線程的基本概念

進程:? 可以理解成一個運行中的應用程序,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎,主要管理資源.

線程:? 是進程的基本執行單元,一個進程對應多個線程.

主線程:? 處理UI,所有更新UI的操作都必須在主線程中執行,不要把耗時操作放在主線程,會卡界面.

多線程:? 在同一時刻,一個CPU只能處理1條線程,但是CPU可以在多條線程之間快速切換,只要切換的足夠快,就造成了多線程一同執行的假象.

多線程是通過提高資源使用率來提高總體的效率.

我們運用多線程的目的是:將耗時操作放在后臺運行.

二,線程狀態與生命周期

下圖是線程狀態示意圖,從圖中可以看出線程的生命周期是:? 新建 -就緒 -運行 - 阻塞 - 死亡

下面是生命周期的每一步:

新建:?? 實例化線程對象.

就緒:? 向線程對象發送start消息,線程對象被加入線程池等待CPU調度.

運行:? CPU負責調度可調度線程池中線程的執行.線程執行完成前,狀態可能會在就緒和運行之間來回切換.就緒和運行之間的狀態變化由CPU負責,程序不能干預.

阻塞:? 當滿足某個預定條件時,可以使用休眠或鎖,阻塞線程執行,sleepForTimeInterval(休眠指定時間), sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥鎖).

還有線程的exit和cancel.

[NSThread exit]:一旦強行終止線程,后續的所有代碼都不會被執行.

[thread cancel]取消:? 并不會直接取消線程,只是給線程添加isCancelled標記.

三,多線程的四種解決方案

多線程的四種解決方案分別是:pthread,NSThread, GCD,NSOperation.

下面是對這四種方案進行了解讀和對比.


四,線程安全問題

當多個線程訪問同一塊資源時,很容易引發數據錯亂和數據安全問題.就好比幾個人在同一時修改一個表格,造成數據的錯亂.

解決多線程安全問題的方法

方法一:? 互斥鎖(同步鎖)

@synchronized(鎖對象) {
??????? // 需要鎖定的代碼
? }

判斷的時候鎖對象要存在,如果代碼中只有一個地方需要加鎖,大多時候使用self作為鎖對象,這樣可以避免單獨再創建一個鎖對象.

加了互斥鎖的代碼,當新線程訪問時,如果發現其他線程正在執行鎖定的代碼,新線程就會進入休眠

方法二:? 自旋鎖

加了自旋鎖,當新線程訪問代碼時,如果發現其他線程正在鎖定代碼,新線程會用死循環的方式,一直等待鎖定的代碼執行完成.相當于不停嘗試執行代碼,比較小號性能.

屬性修飾atomic本身就是一把自旋鎖.

下面說一下屬性修飾nonatomic 和atomic

nonatomic 非原子性的,同一時間可以有很多線程讀和寫.

atomic原子屬性(線程安全), 保證同一時間只有一個縣城能夠寫入(但是同一個時間多個線程都可以取值),atomic本身就有一把鎖(自旋鎖).

atomic: 線程安全,需消耗大量的資源

nonatomic: 非線程安全,不過效率更高,一般用nonatomic

五,NSThread的使用

No.1:NTHread創建線程

NSThread有三種創建方式:

init方式

detachNewThreadSelector創建好之后自動自動

pefromSelectorInBackground創建好之后也是直接啟動

/** 方法一, 需要Start */

? ? NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething1:) object:@"NSThread1"];

? ? //線程加入線程池等待CPU調度,時間很快,幾乎是立刻執行

? ? [thread1 start];

/** 方法二, 創建好之后自動啟動 */

? ? [NSThread detachNewThreadSelector:@selector(doSomething2:) toTarget:self withObject:@"NSThread2"];

/** 隱式創建, 直接啟動 */

? ? [self performSelectorInBackground:@selector(doSomething3:) withObject:@"NSThread3"];

No.2:NSThread的類方法

返回當前線程

//?當前線程

[NSThread?currentThread];

NSLog(@"%@",[NSThread?currentThread]);


//?如果number=1,則表示在主線程,否則是子線程

打印結果:{number?=?1,?name?=?main}

阻塞休眠

//休眠多久

[NSThread?sleepForTimeInterval:2];

//休眠到指定時間

[NSThread?sleepUntilDate:[NSDate?date]];

類方法補充

//退出線程

[NSThread?exit];

//判斷當前線程是否為主線程

[NSThread?isMainThread];

//判斷當前線程是否是多線程

[NSThread?isMultiThreaded];

//主線程的對象

NSThread?*mainThread?=?[NSThread?mainThread];

No.3:NSThread的一些屬性

//線程是否在執行

thread.isExecuting;

//線程是否被取消

thread.isCancelled;

//線程是否完成

thread.isFinished;

//是否是主線程

thread.isMainThread;

//線程的優先級,取值范圍0.0到1.0,默認優先級0.5,1.0表示最高優先級,優先級高,CPU調度的頻率高

?thread.threadPriority;

六,GCD的理解與使用

No.1:GCD的特點

GCD會自動利用更多的CPU內核;

GCD自動管理線程的生命周期(創建線程, 調度任務, 銷毀線程);

程序員只需要告訴GCD想要如何執行什么任務,不需要任何線程管理代碼.

No.2: GCD的基本概念

任務 (block) : 任務就是將要在線程中執行的代碼,將這段代碼用block封裝好,然后將這個任務添加到指定的執行方式(同步執行,異步執行), 等待CPU從隊列中取出任務放到對應的線程中執行.

同步 (sync):一個接著一個,前一個沒有執行完, 后面的不能執行,不開線程.

異步 (async) : 開啟多線程, 任務同一時間可以一起執行. 異步是多線程的代名詞.

隊列 :? 裝載線程任務的隊形結. (系統以先進先出的方式調度隊列中的任務執行). 在GCD中有兩種隊列: 串行隊列和并發隊列.

串行隊列 :線程只能依次有序的執行.

并發隊列 :線程可以同時一起執行.實際上是CPU在多條線程之間快速切換.(并發只有在異步(dispatch_async)函數下才有效).

GCD總結 : 將任務(要在線程中執行的操作block) 添加到隊列(自己創建或使用全局并發隊列),并且指定任務的執行方式(異步dispatch_async, 同步dispatch_sync).

No.3 : 隊列的創建方法

使用dispatch_queue_create來創建隊列對象,傳入兩個參數,第一個表示隊列的唯一標示符,可為空.第二個參數用來標示串行隊列(DISPATCH_QUEUE_SERIAL) 或并發隊列 (DISPATCH_QUEUE_CONCURRENT).

//串行隊列
??? dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
??? //并發隊列
??? dispatch_queue_t queue1 = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

GCD隊列還有另外兩種:

主隊列,全局并發隊列

主隊列 :主隊列是負責在主線程上調度任務, 如果在主線程上已經有任務正在執行,主隊列會等到主線程空閑后再調度任務, 通常是返回主線程更新UI的時候使用. dispatch_get_main_queue()

dispatch_async(dispatch_get_global_queue(0, 0), ^{

? ? ? ? //耗時操作放在這里


? ? ? ? dispatch_async(dispatch_get_main_queue(), ^{

? ? ? ? ? ? //回到主線程進行UI操作


? ? ? ? });

? ? });

全局并發隊列: 全局并發隊列就是一個并發隊列,是為了讓我們更方便的使用多線程.

dispatch_get_global_queue(0,0).

//全局并發隊列

dispatch_queue_t?queue?=?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0);

//全局并發隊列的優先級

#define?DISPATCH_QUEUE_PRIORITY_HIGH?2?//?高優先級

#define?DISPATCH_QUEUE_PRIORITY_DEFAULT?0?//?默認(中)優先級

#define?DISPATCH_QUEUE_PRIORITY_LOW?(-2)?//?低優先級

#define?DISPATCH_QUEUE_PRIORITY_BACKGROUND?INT16_MIN?//?后臺優先級

//iOS8開始使用服務質量,現在獲取全局并發隊列時,可以直接傳0

dispatch_get_global_queue(0,?0);

No.4:同步/異步/任務、創建方式

同步 (sync) 使用dispatch_sync來表示

異步 (sync) 使用dispatch_async來表示

任務就是將要在線程中執行的代碼, 將這段代碼用block封裝好.

代碼如下:

//同步執行任務

? ? dispatch_sync(dispatch_get_global_queue(0, 0), ^{

? ? ? ? //任務放在這個block里

? ? ? ? NSLog(@"我是同步執行任務");

? ? });

? ? //異步執行任務

? ? dispatch_async(dispatch_get_global_queue(0, 0), ^{

? ? ? ? //

? ? ? ? //任務放在這個block里

? ? ? ? NSLog(@"我是異步執行任務");

? ? });

No.5: GCD的使用

由于有多種隊列(串行/并發/主隊列) 和兩種執行方式 (同步/異步),所以他們之間可以有多種組合方式.

1, 串行同步

2,串行異步

3,并發同步

4,并發異步

5,主隊列同步

6,主隊列異步

◎串行同步

執行完一個任務,在執行下一個任務,不開啟新線程.

/**?串行同步?*/

-?(void)syncSerial?{


????NSLog(@"\n\n**************串行同步***************\n\n");


????//?串行隊列

????dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_SERIAL);


????//?同步執行

????dispatch_sync(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"串行同步1???%@",[NSThread?currentThread]);

????????}

????});

????dispatch_sync(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"串行同步2???%@",[NSThread?currentThread]);

????????}

????});

????dispatch_sync(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"串行同步3???%@",[NSThread?currentThread]);

????????}

????});

}

輸出結果為順序執行,都在主線程:

串行同步1???{number?=?1,?name?=?main}

串行同步1???{number?=?1,?name?=?main}

串行同步1???{number?=?1,?name?=?main}

串行同步2???{number?=?1,?name?=?main}

串行同步2???{number?=?1,?name?=?main}

串行同步2???{number?=?1,?name?=?main}

串行同步3???{number?=?1,?name?=?main}

串行同步3???{number?=?1,?name?=?main}

串行同步3???{number?=?1,?name?=?main}

◎串行異步

開啟新線程,但因為任務是串行的,所以還是按順序執行完任務.

/**?串行異步?*/

-?(void)asyncSerial?{


????NSLog(@"\n\n**************串行異步***************\n\n");


????//?串行隊列

????dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_SERIAL);


????// 異步執行

????dispatch_async(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"串行異步1???%@",[NSThread?currentThread]);

????????}

????});

????dispatch_async(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"串行異步2???%@",[NSThread?currentThread]);

????????}

????});

????dispatch_async(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"串行異步3???%@",[NSThread?currentThread]);

????????}

????});

}

輸出結果為順序執行,有不同線程:

串行異步1???{number?=?3,?name?=?(null)}

串行異步1???{number?=?3,?name?=?(null)}

串行異步1???{number?=?3,?name?=?(null)}

串行異步2???{number?=?3,?name?=?(null)}

串行異步2???{number?=?3,?name?=?(null)}

串行異步2???{number?=?3,?name?=?(null)}

串行異步3???{number?=?3,?name?=?(null)}

串行異步3???{number?=?3,?name?=?(null)}

串行異步3???{number?=?3,?name?=?(null)}

◎并發同步

因為是同步的,所以執行完一個任務,再執行下一個任務.不會開啟新線程

/**?并發同步?*/

-?(void)syncConcurrent?{

????NSLog(@"\n\n**************并發同步***************\n\n");

????//?并發隊列

????dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_CONCURRENT);

????//?同步執行

????dispatch_sync(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"并發同步1???%@",[NSThread?currentThread]);

????????}

????});

????dispatch_sync(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"并發同步2???%@",[NSThread?currentThread]);

????????}

????});

????dispatch_sync(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"并發同步3???%@",[NSThread?currentThread]);

????????}

????});

}

輸出結果為順序執行,都在主線程:

并發同步1???{number?=?1,?name?=?main}

并發同步1???{number?=?1,?name?=?main}

并發同步1???{number?=?1,?name?=?main}

并發同步2???{number?=?1,?name?=?main}

并發同步2???{number?=?1,?name?=?main}

并發同步2???{number?=?1,?name?=?main}

并發同步3???{number?=?1,?name?=?main}

并發同步3???{number?=?1,?name?=?main}

并發同步3???{number?=?1,?name?=?main}

◎并發異步

任務交替執行,開啟多線程.

/**?并發異步?*/

-?(void)asyncConcurrent?{


????NSLog(@"\n\n**************并發異步***************\n\n");


????//?并發隊列

????dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_CONCURRENT);


????//?同步執行

????dispatch_async(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"并發異步1???%@",[NSThread?currentThread]);

????????}

????});

????dispatch_async(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"并發異步2???%@",[NSThread?currentThread]);

????????}

????});

????dispatch_async(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"并發異步3???%@",[NSThread?currentThread]);

????????}

????});

}

輸出結果為無序執行,有多條線程:

并發異步1???{number?=?3,?name?=?(null)}

并發異步2???{number?=?4,?name?=?(null)}

并發異步3???{number?=?5,?name?=?(null)}

并發異步1???{number?=?3,?name?=?(null)}

并發異步2???{number?=?4,?name?=?(null)}

并發異步3???{number?=?5,?name?=?(null)}

并發異步1???{number?=?3,?name?=?(null)}

并發異步2???{number?=?4,?name?=?(null)}

并發異步3???{number?=?5,?name?=?(null)}

◎ 主隊列同步

如果在主線程中運用這種方式,則會發生死鎖,程序崩潰.

/**?主隊列同步?*/

-?(void)syncMain?{


????NSLog(@"\n\n**************主隊列同步,放到主線程會死鎖***************\n\n");


????//?主隊列

????dispatch_queue_t?queue?=?dispatch_get_main_queue();


????dispatch_sync(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"主隊列同步1???%@",[NSThread?currentThread]);

????????}

????});

????dispatch_sync(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"主隊列同步2???%@",[NSThread?currentThread]);

????????}

????});

????dispatch_sync(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"主隊列同步3???%@",[NSThread?currentThread]);

????????}

????});

}

主隊列同步造成死鎖的原因

1,如果在主線程中運用主隊列同步,也就是把任務放到主線程的隊列中.

2,而同步對于任務是立刻執行的,那么當把第一個任務放進主隊列時,他就立馬執行

3,可是主線程現在正在處理syncMain方法,任務需要等sybcMain執行完才能執行.

4,sysnMain執行到第一個任務的時候,又要等第一個任務執行完才能往下執行第二個和第三個任務.

5,這樣syncMain方法和第一個任務就開始了互相等待,形成了死鎖.

◎主隊列異步

在主線程中任務按順序執行

/**?主隊列異步?*/

-?(void)asyncMain?{


????NSLog(@"\n\n**************主隊列異步***************\n\n");


????//?主隊列

????dispatch_queue_t?queue?=?dispatch_get_main_queue();


????dispatch_sync(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"主隊列異步1???%@",[NSThread?currentThread]);

????????}

????});

????dispatch_sync(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"主隊列異步2???%@",[NSThread?currentThread]);

????????}

????});

????dispatch_sync(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"主隊列異步3???%@",[NSThread?currentThread]);

????????}

????});

}

輸出結果為在主線程中按順序執行:

主隊列異步1???{number?=?1,?name?=?main}

主隊列異步1???{number?=?1,?name?=?main}

主隊列異步1???{number?=?1,?name?=?main}

主隊列異步2???{number?=?1,?name?=?main}

主隊列異步2???{number?=?1,?name?=?main}

主隊列異步2???{number?=?1,?name?=?main}

主隊列異步3???{number?=?1,?name?=?main}

主隊列異步3???{number?=?1,?name?=?main}

主隊列異步3???{number?=?1,?name?=?main}

◎GCD之間的通訊

開發中需要在主線程上進行UI的相關操作,通常會把一些耗時的操作放在其他線程,比如說圖片文件下載等耗時操作.當完成了耗時操作之后,需要回到主線程進行UI的處理,這里用到了線程之間的通訊.

-?(IBAction)communicationBetweenThread:(id)sender?{


????//?異步

????dispatch_async(dispatch_get_global_queue(0,?0),?^{

????????//?耗時操作放在這里,例如下載圖片。(運用線程休眠兩秒來模擬耗時操作)

????????[NSThread?sleepForTimeInterval:2];

????????NSString?*picURLStr?=?@"http://www.bangmangxuan.net/uploads/allimg/160320/74-160320130500.jpg";

????????NSURL?*picURL?=?[NSURL?URLWithString:picURLStr];

????????NSData?*picData?=?[NSData?dataWithContentsOfURL:picURL];

????????UIImage?*image?=?[UIImage?imageWithData:picData];


????????//?回到主線程處理UI

????????dispatch_async(dispatch_get_main_queue(),?^{

????????????//?在主線程上添加圖片

????????????self.imageView.image?=?image;

????????});

????});

}

上面的代碼實在新開的線程中進行圖片下載,下載完成之后回到主線程顯示圖片.

◎GCD柵欄

當任務需要異步進行,但是這些任務需要分成兩組來完成,第一組完成之后才能進行第二組, 這時候就用到了GCD的柵欄方法

dispatch_barrier_async.

-?(IBAction)barrierGCD:(id)sender?{


????//?并發隊列

????dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_CONCURRENT);


????//?異步執行

????dispatch_async(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"柵欄:并發異步1???%@",[NSThread?currentThread]);

????????}

????});

????dispatch_async(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"柵欄:并發異步2???%@",[NSThread?currentThread]);

????????}

????});


????dispatch_barrier_async(queue,?^{

????????NSLog(@"------------barrier------------%@",?[NSThread?currentThread]);

????????NSLog(@"*******?并發異步執行,但是34一定在12后面?*********");

????});


????dispatch_async(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"柵欄:并發異步3???%@",[NSThread?currentThread]);

????????}

????});

????dispatch_async(queue,?^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"柵欄:并發異步4???%@",[NSThread?currentThread]);

????????}

????});

}

上面的代碼打印結果如下,開了多條線程,所有任務都是并發異步進行.但是第一組完成之后,才會進行第二組的操作.

柵欄:并發異步1???{number?=?3,?name?=?(null)}

柵欄:并發異步2???{number?=?6,?name?=?(null)}

柵欄:并發異步1???{number?=?3,?name?=?(null)}

柵欄:并發異步2???{number?=?6,?name?=?(null)}

柵欄:并發異步1???{number?=?3,?name?=?(null)}

柵欄:并發異步2???{number?=?6,?name?=?(null)}

?------------barrier------------{number?=?6,?name?=?(null)}

*******?并發異步執行,但是34一定在12后面?*********

柵欄:并發異步4???{number?=?3,?name?=?(null)}

柵欄:并發異步3???{number?=?6,?name?=?(null)}

柵欄:并發異步4???{number?=?3,?name?=?(null)}

柵欄:并發異步3???{number?=?6,?name?=?(null)}

柵欄:并發異步4???{number?=?3,?name?=?(null)}

柵欄:并發異步3???{number?=?6,?name?=?(null)}

◎GCD延時執行

當需要等待一會再執行,就可以用到這個方法了:dispatch_after

dispatch_after(dispatch_time(DISPATCH_TIME_NOW,?(int64_t)(5.0?*?NSEC_PER_SEC)),?dispatch_get_main_queue(),?^{

????//?5秒后異步執行

????NSLog(@"我已經等待了5秒!");

});

GCD實現代碼只執行一次

使用dispatch_once能保證某段代碼在程序運行過程中只被執行1次。可以用來設計單例。

static?dispatch_once_t?onceToken;

dispatch_once(&onceToken,?^{

????NSLog(@"程序運行過程中我只執行了一次!");

});

◎GCD快速迭代

GCD有一個快速迭代的方法dispatch_apply, dispatch_apply可以同時遍歷多個數字

-?(IBAction)applyGCD:(id)sender?{


????NSLog(@"\n\n**************?GCD快速迭代?***************\n\n");


????//?并發隊列

????dispatch_queue_t?queue?=?dispatch_get_global_queue(0,?0);


????//?dispatch_apply幾乎同時遍歷多個數字

????dispatch_apply(7,?queue,?^(size_t?index)?{

????????NSLog(@"dispatch_apply:%zd======%@",index,?[NSThread?currentThread]);

????});

}

打印結果如下:

dispatch_apply:0======{number?=?1,?name?=?main}

dispatch_apply:1======{number?=?1,?name?=?main}

dispatch_apply:2======{number?=?1,?name?=?main}

dispatch_apply:3======{number?=?1,?name?=?main}

dispatch_apply:4======{number?=?1,?name?=?main}

dispatch_apply:5======{number?=?1,?name?=?main}

dispatch_apply:6======{number?=?1,?name?=?main}

◎GCD隊列組

異步執行幾個耗時操作,當這幾個操作都完成之后再執行另一個操作,就可以用到隊列組了.

隊列組有下面幾個特點:

1,所有的任務會并發的執行(不按順序).

2,所有的異步函數都添加到隊列中,然后再納入隊列組的監聽范圍.

3,使用dispatch_group_notify函數,來監聽上面的任務是否完成,如果完成,就調用這個方法.

隊列組示例代碼:

- (IBAction)groupAction:(id)sender {

? ? NSLog(@"\n\n**************GCD隊列組***************\n");

? ? dispatch_group_t group = dispatch_group_create();

? ? dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

? ? dispatch_group_async(group, queue, ^{

? ? ? ? NSLog(@"隊列組:有一個耗時操作完成!");

? ? });

? ? dispatch_group_async(group, queue, ^{

? ? ? ? NSLog(@"隊列組:有一個耗時操作完成!");

? ? });

? ? dispatch_group_async(group, queue, ^{

? ? ? NSLog(@"隊列組:有一個耗時操作完成!");

? ? });

? ? dispatch_group_notify(group, queue, ^{

? ? ? ? NSLog(@"隊列組:前面的耗時操作都完成了,回到主線程進行相關操作");

? ? });

}

打印結果如下:

隊列組:有一個耗時操作完成!

隊列組:有一個耗時操作完成!

隊列組:有一個耗時操作完成!

隊列組:前面的耗時操作都完成了,回到主線程進行相關操作

至此,GCD的相關操作內容敘述完畢,下面繼續學習NSOperation.

七, NSOperation的理解和使用

No.1: ?NSOperation簡介

NSOperation是基于GCD之上的更高一層封裝,NSOperation需要配合NSOperationQueue來實現多線程.

NSOperation實現多線程步驟如下:

1,創建任務: 先將需要執行的操作封裝到NSOperation對象中.

2,創建隊列: 創建NSOperationQueue.

3,將任務加入到隊列中: 將NSOperation對象添加到NSOperationQueue中

需要注意的是,NAOperation是個抽象類,實際運用時需要使用它的子類,有三種方式:

1,使用子類NSInvocationOperation

2,使用子類NSBlockOperation

3,定義繼承自NSOperation的子類,通過內部響應的方法來封裝任務.

No.2:NSOperation的三種創建方式

◎NSInvocationOperation的使用

創建NSInvocationd對象,并關聯方法,之后start.

-?(void)testNSInvocationOperation?{

????//?創建NSInvocationOperation

????NSInvocationOperation?*invocationOperation?=?[[NSInvocationOperation?alloc]?initWithTarget:self?selector:@selector(invocationOperation)?object:nil];

????//?開始執行操作

????[invocationOperation?start];

}


-?(void)invocationOperation?{

????NSLog(@"NSInvocationOperation包含的任務,沒有加入隊列========%@",?[NSThread?currentThread]);

}

打印結果如下,得到結論: 程序在主線程執行,沒有開啟新線程.

這是因為NSOperation多線程的使用需要配合隊列NSOperationQueue,后面會講到NSOperationQueue的使用.

NSInvocationOperation包含的任務,沒有加入隊列========{number?=?1,?name?=?main}

◎NSBlockOperation的使用

把任務放到NSBlockOperation的block中,然后start.

-?(void)testNSBlockOperation?{

????//?把任務放到block中

????NSBlockOperation?*blockOperation?=?[NSBlockOperation?blockOperationWithBlock:^{

????????NSLog(@"NSBlockOperation包含的任務,沒有加入隊列========%@",?[NSThread?currentThread]);

????}];


????[blockOperation?start];

}

執行結果如下,可以看出:主線程執行,沒有開啟新線程.

同樣的,NSBlockOperation可以配合隊列NSOperationQueue來實現多線程.

NSBlockOperation包含的任務,沒有加入隊列========{number?=?1,?name?=?main}

但是NSBlockOperation有一個addExecutionBlock:,通過這個方法可以讓NSBlockOperation實現多線程.

-?(void)testNSBlockOperationExecution?{

????NSBlockOperation?*blockOperation?=?[NSBlockOperation?blockOperationWithBlock:^{

????????NSLog(@"NSBlockOperation運用addExecutionBlock主任務========%@",?[NSThread?currentThread]);

????}];


????[blockOperation?addExecutionBlock:^{

????????NSLog(@"NSBlockOperation運用addExecutionBlock方法添加任務1========%@",?[NSThread?currentThread]);

????}];

????[blockOperation?addExecutionBlock:^{

????????NSLog(@"NSBlockOperation運用addExecutionBlock方法添加任務2========%@",?[NSThread?currentThread]);

????}];

????[blockOperation?addExecutionBlock:^{

????????NSLog(@"NSBlockOperation運用addExecutionBlock方法添加任務3========%@",?[NSThread?currentThread]);

????}];

????[blockOperation?start];

}

執行結果如下,可以看出,NSBlockOperation創建時block中的任務是在主線程中執行,而運行addExecutionBlock加入的任務是在子線程執行的.

NSBlockOperation運用addExecutionBlock========{number?=?1,?name?=?main}

addExecutionBlock方法添加任務1========{number?=?3,?name?=?(null)}

addExecutionBlock方法添加任務3========{number?=?5,?name?=?(null)}

addExecutionBlock方法添加任務2========{number?=?4,?name?=?(null)}

◎運用繼承自NSOperation的子類

首先我們定義一個繼承自NSOperation的類,然后重寫他的main方法, 之后就可以使用這個子類來進行相關操作了.

/*******************"WHOperation.h"*************************/

#import?@interface?WHOperation?:?NSOperation

@end

/*******************"WHOperation.m"*************************/

#import?"WHOperation.h"

@implementation?WHOperation

-?(void)main?{

????for(int?i?=?0;?i?<?3;?i++)?{

????????NSLog(@"NSOperation的子類WHOperation======%@",[NSThread?currentThread]);

????}

}

@end

/*****************回到主控制器使用WHOperation**********************/

-?(void)testWHOperation?{

????WHOperation?*operation?=?[[WHOperation?alloc]?init];

????[operation?start];

}

運行結果如下,依然是在主線程執行

SOperation的子類WHOperation======{number?=?1,?name?=?main}

NSOperation的子類WHOperation======{number?=?1,?name?=?main}

NSOperation的子類WHOperation======{number?=?1,?name?=?main}

所以NSOperation是需要配合NSOperationQueue來實現多線程的,下面來說一下隊列NSOperationQueue.

No.3: ?隊列NSOperationQueue

NSOperationQueue只有兩種隊列:主隊列、其他隊列.其他隊列包含了串行和并發.

主隊列的創建如下,主隊列上的任務是在主線程中執行的

NSOperationQueue?*mainQueue?=?[NSOperationQueue?mainQueue];

其他隊列(非主隊列)的創建如下,加入到"非主隊列的任務默認就是并發", 開啟多線程

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

注意:

1,非主隊列(其他隊列)可以實現串行或并行.

2,隊列NSOperationQueue有一個參數叫最大并發數: maxConcurrentOperationCount.

3,maxConcurrentOperationCount默認為-1,直接并發執行,所以加入到"非主隊列"中的任務默認就是并發,開啟多線程.

4,當maxConcurrentOperationCount為1時,則表示不開線程,也就是串行.

5,當maxConcurrentOperationCount大于1時,進行并發執行.

6,系統對對打并發數有一個限制,所以即使程序員把maxConcurrentOperationCount設置的很大,系統也會自動調整.所以把最大并發數設置的很大是沒有意義的.

No.4: NSOperation + NSOperationQueue

把任務加入隊列,這才是NSOperation的常規使用

◎addOperation添加任務到隊列

先創建好任務,然后用addOperation:方法把任務添加到隊列,示例代碼如下:

-?(void)testOperationQueue?{

????//?創建隊列,默認并發

????NSOperationQueue?*queue?=?[[NSOperationQueue?alloc]?init];

????//?創建操作,NSInvocationOperation

????NSInvocationOperation?*invocationOperation?=?[[NSInvocationOperation?alloc]?initWithTarget:self?selector:@selector(invocationOperationAddOperation)?object:nil];

????//?創建操作,NSBlockOperation

????NSBlockOperation?*blockOperation?=?[NSBlockOperation?blockOperationWithBlock:^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"addOperation把任務添加到隊列======%@",?[NSThread?currentThread]);

????????}

????}];

? ? [queue?addOperation:invocationOperation];

????[queue?addOperation:blockOperation];

}

-?(void)invocationOperationAddOperation?{

????NSLog(@"invocationOperation===aaddOperation把任務添加到隊列====%@",?[NSThread?currentThread]);

}

運行結果如下,可以看出,任務都是在子線程執行的,開啟了新線程!

invocationOperation===addOperation把任務添加到隊列===={number?=?4,?name?=?(null)}

addOperation把任務添加到隊列======{number?=?3,?name?=?(null)}

addOperation把任務添加到隊列======{number?=?3,?name?=?(null)}

addOperation把任務添加到隊列======{number?=?3,?name?=?(null)}

◎這是一個更方便的把任務添加到隊列的方法,直接把任務寫到block中,添加到任務中.

-?(void)testAddOperationWithBlock?{

????//?創建隊列,默認并發

????NSOperationQueue?*queue?=?[[NSOperationQueue?alloc]?init];


????//?添加操作到隊列

????[queue?addOperationWithBlock:^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"addOperationWithBlock把任務添加到隊列======%@",?[NSThread?currentThread]);

????????}

????}];

}

運行結果如下,任務確實是在子線程中執行

addOperationWithBlock把任務添加到隊列======{number?=?3,?name?=?(null)}

addOperationWithBlock把任務添加到隊列======{number?=?3,?name?=?(null)}

addOperationWithBlock把任務添加到隊列======{number?=?3,?name?=?(null)}

◎運用最大并發數實現串行

上面已經說過,可以運用隊列的的屬性maxConcurrentOperationCount(最大并發數)來實現串行,值需要把它設置為1就可以了,下面我們通過代碼驗證一下.

-?(void)testMaxConcurrentOperationCount?{

????//?創建隊列,默認并發

????NSOperationQueue?*queue?=?[[NSOperationQueue?alloc]?init];

????//?最大并發數為1,串行

????queue.maxConcurrentOperationCount?=?1;

????//?最大并發數為2,并發

//????queue.maxConcurrentOperationCount?=?2;

? ?//?添加操作到隊列

????[queue?addOperationWithBlock:^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"addOperationWithBlock把任務添加到隊列1======%@",?[NSThread?currentThread]);

????????}

????}];

????//?添加操作到隊列

????[queue?addOperationWithBlock:^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"addOperationWithBlock把任務添加到隊列2======%@",?[NSThread?currentThread]);

????????}

????}];

????//?添加操作到隊列

????[queue?addOperationWithBlock:^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"addOperationWithBlock把任務添加到隊列3======%@",?[NSThread?currentThread]);

????????}

????}];

}

運行結果如下,當最大并發數為1的時候,雖然開啟了線程,但是任務是順序執行的,所以實現了串行

No.5: NSOperation的其他操作

◎取消隊列NSOperationQueue的所有操作, NSOperationQueue對象方法

-?(void)cancel

◎使隊列暫停或繼續

//?暫停隊列

[queue?setSuspended:YES];

◎判斷隊列是否暫停

-?(BOOL)isSuspended

暫停和取消不是立刻當前操作,而是等當前操作執行完之后不再進行新的操作.

No.6: NSOperation的操作依賴

NSOperation有一個非常好用的方法, 就是操作依賴.可以從字面意思理解:某一個操作(operetion2)依賴于另一個操作(operation1), 只有當operation1執行完畢,才能執行operation2,這時,就是才做依賴大顯身手的時候了.

-?(void)testAddDependency?{

? ? //?并發隊列

????NSOperationQueue?*queue?=?[[NSOperationQueue?alloc]?init];

? ? //?操作1

????NSBlockOperation?*operation1?=?[NSBlockOperation?blockOperationWithBlock:^{

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"operation1======%@",?[NSThread??currentThread]);

????????}

????}];

? ? //?操作2

????NSBlockOperation?*operation2?=?[NSBlockOperation?blockOperationWithBlock:^{

????????NSLog(@"****operation2依賴于operation1,只有當operation1執行完畢,operation2才會執行****");

????????for(int?i?=?0;?i?<?3;?i++)?{

????????????NSLog(@"operation2======%@",?[NSThread??currentThread]);

????????}

????}];

? ? //?使操作2依賴于操作1

????[operation2?addDependency:operation1];

????//?把操作加入隊列

????[queue?addOperation:operation1];

????[queue?addOperation:operation2];

}

運行結果如下,操作2總是在操作1之后運行,成功驗證了上面的說法

operation1======{number?=?3,?name?=?(null)}

operation1======{number?=?3,?name?=?(null)}

operation1======{number?=?3,?name?=?(null)}

****operation2依賴于operation1,只有當operation1執行完畢,operation2才會執行****

operation2======{number?=?4,?name?=?(null)}

operation2======{number?=?4,?name?=?(null)}

operation2======{number?=?4,?name?=?(null)}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容