前言
本文主要介紹iOS多線程開發中使用的主要技術:NSOperation, GCD, NSThread, pthread。 内容按照開發中的優先推薦使用的順序進行介紹,涉及多線程底層知識比較多的NSThread, pthread 放到了後面,建議小夥伴們先看目錄,根據自己的需求來閱讀。
NSOperation
簡介
使用NSOperation和NSOperationQueue能簡單高效的實作多線程程式設計
NSOperation和NSOperationQueue實作多線程的具體步驟:
(1)先将需要執行的操作封裝到一個NSOperation對象中
(2)然後将NSOperation對象添加到NSOperationQueue中
(3)系統會⾃動将NSOperationQueue中的NSOperation取出來
(4)将取出的NSOperation封裝的操作放到⼀條新線程中執⾏
NSOperation的子類
NSOperation是個抽象類,并不具備封裝操作的能力,必須使⽤它的子類
(1)不能直接使用(方法沒有實作)
(2)限制子類都具有共同的屬性和方法
使用NSOperation⼦類的方式有3種:
(1)NSInvocationOperation
(2)NSBlockOperation
(3)自定義子類繼承NSOperation,實作内部相應的⽅法
NSInvocationOperation
建立NSInvocationOperation 對象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
調用start 方法開始執行操作
- (void)start;
一旦執行操作,就會調用target 的 selector 方法
注意:
預設情況下,調用start方法後并不會開一條新線程去執行操作,而是目前線程同步執行操作;隻有将NSOperation 添加到NSOperationQuene中,才會異步執行操作;
1. 執行操作
//建立操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector: @selector(downloadFile:) object:@"fileName"]; //在目前線
//程執行方法(開始執行操作)
[op start];
2. 把操作添加到隊列(并開始異步執行)
//建立操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector: @selector(downloadFile:) object:@"fileName"];
//将操作添加到隊列,會自動異步調用方法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op];
- (void)downloadFile:(id)object
{
NSLog(@"下載下傳:%@----線程:%@",object,[NSThread currentThread]);
}
3. 開啟多個線程,不會順序執行
我們要記住:
NSOperation是對GCD的封裝,而GCD并發隊列,異步執行。
//隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 10; i++)
{
//建立操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector: @selector(downloadFile:) object:@(i)];
//将操作添加到隊列,會自動異步調用方法
[queue addOperation:op];
}
- (void)downloadFile:(id)object
{
NSLog(@"下載下傳:%@----線程:%@",object,[NSThread currentThread]);
}
注意:
預設情況下,如果操作沒有放到隊列中queue中,都是同步執行。隻有将NSOperation放到一個NSOperationQueue中,才會異步執行操作
添加操作到NSOperationQueue中
- (void)addNOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void(^)(void))block
NSBlockOperation
建立NSBlockOperation對象
+ (id)blockOperationWithBlock:(void)^(void)
通過addExecutionBlock 方法添加更多的操作
- (id)addExecutionBlock:(void)(^)(void)block
注意:隻要在NSBlockOperation封裝的操作數>1, 就會一步執行操作;
1. NSBlockOperation
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 10; i++)
{
//建立操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"down %d %@",i,[NSThread currentThread]);
}];
//把操作添加到隊列中
[queue addOperation:op];
}
2. NSOperationQueue添加block的operation
下面我們直接把操作的block代碼塊加到隊列中,是不是代碼更簡潔啦,block是不是用起來很爽-_-
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 10; i++)
{
[queue addOperationWithBlock:^{
NSLog(@"down %d %@",i,[NSThread currentThread]);
}];
}
3. 全局操作隊列
下面我們定義一個全局隊列來排程所有的異步操作
@property (nonatomic, strong) NSOperationQueue *queue;
//懶加載隊列
- (NSOperationQueue *)queue
{
if (_queue == nil)
{
_queue = [[NSOperationQueue alloc] init];
}
return _queue;
}
for (int i = 0; i < 10; i++)
{
[self.queue addOperationWithBlock:^{
NSLog(@"down %d %@",i,[NSThread currentThread]);
}];
}
4. 監聽操作完成
[op1 setCompletionBlock:^{
NSLog(@".....");
}];
并發數
- 并發數:同時執⾏行的任務數.比如,同時開3個線程執行3個任務,并發數就是3
- 最大并發數:同一時間最多隻能執行的任務的個數。
最⼤大并發數的相關⽅方法
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
注意:
- 如果沒有設定最大并發數,那麼并發的個數是由系統記憶體和CPU決定的,可能記憶體多就會開多一點,記憶體少就開少一點。
- num的值并不代表線程的個數,僅僅代表線程的ID。
- 最大并發數不要亂寫(5以内),不要開太多,一般以2~3為宜,因為雖然任務是在子線程進行處理的,但是cpu處理這些過多的子線程可能會影響UI,讓UI變卡。
- (void)demo
{
self.queue.maxConcurrentOperationCount = 3;
for (int i=1; i<50; i++)
{
[self.queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:3.0];
NSLog(@"====%@===%d",[NSThread currentThread],i);
}];
[NSThread sleepForTimeInterval:1.0];
}
}
不加最大并發數:
- 此時會建立很多線程,線程數越多說明線程池更大了。但是線程越多越耗資源,配置設定線程的時間也就越多。是以使用線程的時候要合适最好
- GCD通常隻會開啟5~6個線程
- 通過設定sleepForTimeInterval可以延遲線程執行時間,也可以減少線程數,但是一來于操作的執行時間;
添加最大并發數:
- 把操作添加到隊列
[ self.queue addOperationWithBlock]
- 去線程池去取空閑的線程,如果沒有就建立線程
- 把操作交給從線程池中取出的線程執行
- 執行完成後,把線程再放回線程池中
- 重複2,3,4直到所有的操作都執行完
隊列的取消,暫停和恢複
取消隊列的所有操作
- (void)cancelAllOperations;
提⽰:
也可以調用NSOperation的 - (void)cancel;
⽅法取消單個操作
暫停和恢複隊列
- (void)setSuspended:(BOOL)b; // YES代表暫停隊列,NO代表恢複隊列
- (BOOL)isSuspended; //目前狀态
暫停和恢複的适用場合
在tableview界面,開線程下載下傳遠端的網絡界面,對UI會有影響,使使用者體驗變差。那麼這種情況,就可以設定在使用者操作UI(如滾動螢幕)的時候,暫停隊列(不是取消隊列),停止滾動的時候,恢複隊列。
示例代碼-搖獎機
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiM0cDOycDMyADMxYDM1EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *lbl0;
@property (weak, nonatomic) IBOutlet UILabel *lbl1;
@property (weak, nonatomic) IBOutlet UILabel *lbl2;
@property (weak, nonatomic) IBOutlet UIButton *btn;
//全局隊列
@property (nonatomic, strong) NSOperationQueue *queue;
- (IBAction)start:(UIButton *)sender;
@end
@implementation ViewController
//隊列懶加載-> 如果沒有初始話queue, 則不會有隊列生成,程式不會有響應
- (NSOperationQueue *)queue
{
if (_queue == nil)
{
_queue = [[NSOperationQueue alloc] init];
}
return _queue;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)start:(UIButton *)sender
{
//首先要判斷隊列是否為空->判斷隊列是否是挂起狀态時,并不會判斷隊列中是否有操作
if(self.queue.operationCount == 0)
{
//如果操作為空,說明執行搖獎,同時開始按鈕變暫停
[self.queue addOperationWithBlock:^{
[self random];
}];
[self.btn setTitle:@"停止" forState:UIControlStateNormal];
//目前隊列執行,不被挂起
self.queue.suspended = NO;
}
else if(self.queue.isSuspended)
{
//如果目前是挂起狀态->開始隊列->btn:暫停
self.queue.suspended = NO;
[self.btn setTitle:@"停止" forState:UIControlStateNormal];
}
else
{
//如果目前是運作狀态->暫停隊列->btn:開始
self.queue.suspended = YES;
[self.btn setTitle:@"開始" forState:UIControlStateNormal];
}
}
- (void)random
{
//如果單簽隊列沒有挂起
while (![NSOperationQueue currentQueue].isSuspended)
{
int num = arc4random_uniform(10);
int num1 = arc4random_uniform(10);
int num2 = arc4random_uniform(10);
[NSThread sleepForTimeInterval:0.05];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.lbl0.text = [NSString stringWithFormat:@"%d",num];
self.lbl1.text = [NSString stringWithFormat:@"%d",num1];
self.lbl2.text = [NSString stringWithFormat:@"%d",num2];
}];
}
}
@end
操作優先級
設定NSOperation在queue中的優先級,可以改變操作的執⾏優先級
- (NSOperationQueuePriority)queuePriority;
- (void)setQueuePriority:(NSOperationQueuePriority)p;
優先級的取值
- NSOperationQueuePriorityVeryLow = -8L,
- NSOperationQueuePriorityLow = -4L,
- NSOperationQueuePriorityNormal = 0,
- NSOperationQueuePriorityHigh = 4,
- NSOperationQueuePriorityVeryHigh = 8
說明:優先級高的任務,調用的幾率會更大。
操作依賴
NSOperation之間可以設定依賴來保證執行順序,⽐如一定要讓操作A執行完後,才能執行操作B:
[opB addDependency:opA];
可以在不同queue的NSOperation之間建立依賴關系
注意:
不能循環依賴(不能A依賴于B,B又依賴于A)
1. 模拟軟體的部分更新
/*=======依賴關系========*/
/**
*模拟軟體的部分更新:下載下傳->解壓->通知使用者更新
*/
//下載下傳壓縮包-> 操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載下傳 %@",[NSThread currentThread]);
}];
//解壓,複制到相應目錄
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"解壓 %@",[NSThread currentThread]);
}];
//通知使用者
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"通知使用者更新完成 %@",[NSThread currentThread]);
}];
//設定操作的依賴關系
[op2 addDependency:op1];
[op3 addDependency:op2];
//添加操作
/**
*waitUntilFinished YES 等待所有的操作執行完成 會阻塞窗體的執行
*waitUntilFinished NO 不等待
*/
[self.queue addOperations:@[op1,op2,op3] waitUntilFinished:NO];
NSLog(@"over");
循環依賴
發生循環依賴,程式不會死鎖。界面也不會阻塞,操作不會執行
[op2 addDependency:op1];
[op3 addDependency:op2];
[op1 addDependency:op3];
3. 依賴關系可以跨隊列執行
[op2 addDependency:op1];
[op3 addDependency:op2];
//子隊列
[self.queue addOperations:@[op1,op2] waitUntilFinished:NO];
NSLog(@"over");
[[NSOperationQueue mainQueue] addOperation:op3];
提示
任務添加的順序并不能夠決定執行順序,執行的順序取決于依賴。使用Operation的目的就是為了讓開發人員不再關心線程。
操作的監聽
可以監聽一個操作的執行完畢:
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;
- (void)demo
{
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
for (int i=1; i<3; i++)
{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%@==download image = %d",[NSThread currentThread], i);
}
}];
[self.queue addOperation:op];
op.completionBlock =^{
NSLog(@"finishing download image, do ot
her things");
};
NSLog(@"%@====I'm later than downloading image",[NSThread currentThread]);
}
注意:
- 下載下傳圖檔的操作在子線程中執行,op隻是添加到隊列裡了,至于什麼時候執行需要等待相應的線程;
- 是以第三個輸出出現在最前面,且在主線程中;
- completionBlock中的操作要等待下載下傳圖檔完成;
GCD和NSOperation的比較
GCD
- GCD是iOS4.0推出的 ,主要針對多核cpu做了優化,是C語言的技術
- GCD是将任務(block)添加到隊列(串行/并行/全局/主隊列),并且以同步/異步的方式執行任務的函數
GCD提供了一些NSOperation不具備的功能
- 一次性執行
- 延遲執行
- 排程組
NSOperation
- NSOperation是iOS2.0推出的,iOS4之後重寫了NSOperation
- NSOperation将操作(異步的任務)添加到隊列(并發隊列),就會執行指定操作的函數
NSOperation裡提供的友善的操作 :
- 最大并發數
- 隊列的暫停/繼續
- 取消所有的操作
- 指定操作之間的依賴關系(GCD可以用同步實作)