天天看點

玩轉iOS開發 - 多線程開發前言NSOperationGCD和NSOperation的比較困了,待續,近期更新……GCDNSThreadPthread

前言

本文主要介紹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(如滾動螢幕)的時候,暫停隊列(不是取消隊列),停止滾動的時候,恢複隊列。

示例代碼-搖獎機

玩轉iOS開發 - 多線程開發前言NSOperationGCD和NSOperation的比較困了,待續,近期更新……GCDNSThreadPthread
#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可以用同步實作)

困了,待續,近期更新……

GCD

NSThread

Pthread