iOS開發之多線程技術(NSThread、OperationQueue、GCD)


  在前面的博客中如果用到了異步請求的話,也是用到的第三方的東西,沒有正兒八經的用過iOS中多線程的東西。其實多線程的東西還是蠻重要的,如果對於之前學過操作系統的小伙伴來說,理解多線程的東西還是比較容易的,今天就做一個小的demo來詳細的了解一下iOS中的多線程的東西。可能下面的東西會比較枯燥,但還是比較實用的。

  多線程用的還是比較多的,廢話少說了,下面的兩張截圖是今天我們實驗的最終結果,應該是比較全的,小伙伴們由圖來分析具體的功能吧:

  功能說明:

    1、點擊同步請求圖片,觀察整個UI界面的變化,並點擊測試按鈕,紅色是否會變成綠色。  

    2、NSThread按鈕,是由NSThread方式創建線程並執行相應的操作。

    3、Block操作按鈕是用Block創建操作,並在操作隊列中執行,下面的是Invocation操作

    4、serial是GCD中的串行隊列,concurrent是GCD中的並行隊列

  好啦,上面的咸蛋先到這兒,代碼該走起啦。

  一、准備階段

     1.不管使用代碼寫,還是storyboard或者xib等,先把上面所需的控件初始化好以便使用

     2.點擊測試UI按鈕,改變下邊label的顏色的代碼如下:

 1 //改變lable的顏色,在紅綠顏色之間進行交換
 2 - (IBAction)tapTestButton:(id)sender {
 3     static int i = 1;
 4     if (i == 1) {
 5         _testLabel.backgroundColor = [UIColor redColor];
 6         i = 0;
 7     }
 8     else
 9     {
10         _testLabel.backgroundColor = [UIColor greenColor];
11         i = 1;
12     }
13     
14 }

 

    3.從網絡上獲取圖片,並使用主線程顯示進程調用情況

 1 //從wang'lu獲取圖片數據
 2 -(NSData *) getImageData
 3 {
 4     
 5     _count ++;
 6     int count = _count;
    //線程開始啟動
7   NSString *str = [NSString stringWithFormat:@"%d.線程%@",count,[NSThread currentThread]];
    NSLog(@"%@",str);
 8     NSData *data;
 9     [NSThread sleepForTimeInterval:0.5];
10     data = [NSData dataWithContentsOfURL:[NSURL URLWithString:IMAGEURL]];
11 
12     NSString *str = [NSString stringWithFormat:@"%d.線程%@完畢",count,[NSThread currentThread]];
13     //請求數據的任務由其他線程解決,所以LogTextView的內容由主線程更新,也只有主線程才能更新UI
14     [self performSelectorOnMainThread:@selector(updateTextViewWithString:) withObject:str waitUntilDone:YES];
15     return data;
16 }

 

    4.上面的用到了主線程來調用updateTextViewWithString方法,因為只有主線程才能更新UI,updateTextViewWithString:這個方法負責把線程的執行信息顯示在View上,代碼如下:

1 //在ViewController上顯示圖片請求情況
2 -(void)updateTextViewWithString:(NSString *)str
3 {
4     NSString *old_str = [NSString stringWithFormat:@"%@\n%@",_logTextView.text, str];
5     
6     _logTextView.text = old_str;
7     //改變Label的顏色,便於觀察
8     [self tapTestButton:nil];
9 }

 

    5.把請求完的圖片加載到ImageView上

1 //更新圖片
2 -(void) updateImageWithData:(NSData *)data
3 {
4     UIImage *image = [UIImage imageWithData:data];
5     [_testImage setImage:image];
6 }

 

    6.加載圖片的,也就是請求數據后在ImageView上顯示

1 //由其他線程請求數據,由主線程來更新UI
2 -(void)loadImageWithThreadName:(NSString *)threadName
3 {
4     [[NSThread currentThread] setName:threadName];
5     
6     NSData *data = [self getImageData];
7     [self performSelectorOnMainThread:@selector(updateImageWithData:) withObject:data waitUntilDone:YES];
8 }

 

  二、通過各種方式來

    1.同步請求圖片測試,請求數據和更新UI都放在主線程中順序執行,這樣在請求數據的時候UI會卡死,代碼如下;

1 //同步請求圖片,視圖阻塞的,因為主線程被占用,無法進行視圖的更新
2 - (IBAction)tapButton:(id)sender {
3     NSData *data = [self getImageData];
4     [self updateImageWithData:data];
5 }

 

    2.NSThread創建線程測試,用detachNewThreadSelector方法來創建新的線程會自動啟動並執行,而不用調用start方法。代碼如下:

1 //NSThread
2 - (IBAction)tapButton2:(id)sender {
3     //點擊一次button就創建一個新的線程來請求圖片數據
4     for (int i = 0;i < 10; i ++) {
5         [NSThread detachNewThreadSelector:@selector(loadImageWithThreadName:) toTarget:self withObject:@"NSThread"];
6     }
7  }

 

   

    3.NSInvocationOperation的使用,新建一個調用操作,然后添加到隊列中執行,代碼如下:

 1 //NSInvocationOperation
 2 - (IBAction)tapInvocationOperation:(id)sender {
 3  
 4     
 5     //上面的調用操作需要放到調用隊列里才執行的
 6     //創建操作隊列
 7     NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
 8 
 9     for (int i = 0;i < 10; i ++) {
10         //實例化一個調用操作,來執行數據請求
11         NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImageWithThreadName:) object:@"Invocation"];
12         //把上面的調用操作放到操作隊列里,隊列會自動開啟一個線程調用我們指定的方法
13         [operationQueue addOperation:invocationOperation];
14     }
15 }

 

 

    4.block的操作,新建一個block操作,並添加到隊列中執行,代碼如下:

 1 //BlockOperation
 2 - (IBAction)tapBlockOperation:(id)sender {
 3     __weak __block ViewController *copy_self = self;
 4     
 5     //創建BlockOperation
 6     NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
 7         [copy_self loadImageWithThreadName:@"Block"];
 8     }];
 9     
10     //添加到操作隊列
11     NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
12     [operationQueue addOperation:blockOperation];
13     
14     for (int i = 0;i < 10; i ++) {
15     
16         //另一種方式
17         [operationQueue addOperationWithBlock:^{
18             [copy_self loadImageWithThreadName:@"Block"];
19         }];
20     }
21 }

 

 

    5.GCD中的串行隊列:

 1 //串行隊列
 2 - (IBAction)tapGCDserialQueue:(id)sender {
 3     //創建串行隊列
 4     dispatch_queue_t serialQueue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
 5     
 6     
 7     __weak __block ViewController *copy_self = self;
 8     
 9     
10     for (int i = 0;i < 10; i ++) {
11         //異步執行隊列
12         dispatch_async(serialQueue, ^{
13             [copy_self loadImageWithThreadName:@"Serial"];
14         });
15     }
16     
17     
18 }

 

 

    6.GCD中的並行隊列:

 1 //並行隊列
 2 - (IBAction)tapGCDConcurrentQueue:(id)sender {
 3     //創建並行隊列
 4     dispatch_queue_t concurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
 5     __weak __block ViewController *copy_self = self;
 6     
 7     for (int i = 0;i < 10; i ++) {
 8         //異步執行隊列
 9         dispatch_async(concurrentQueue, ^{
10             [copy_self loadImageWithThreadName:@"Concurrent"];
11         });
12 
13     }
14     
15 }

 

    以上是各個按鈕對應的方法,下面的截圖是執行結果:

 

  三、線程間的同步問題(為我們的線程添加上同步鎖)

    在操作系統中講多線程時有一個名詞叫臟數據,就是多個線程操作同一塊資源造成的,下面就修改一下代碼,讓數據出現問題,然后用同步鎖來解決這個問題

    1.在getImageData方法(標題一中的第3個方法)中有兩條語句。這個用來顯示線程的標號。上面的標號是沒有重復的。

1     _count ++;
2     int count = _count;

    在兩條語句中間加一個延遲,如下:

    _count ++;
    [NSThread sleepForTimeInterval:1];
    int count = _count;

    如果運行的話,會有好多標號是重復的,如圖一,__count是成員變量,多個線程對此他進行操作,所以會出現標號不一致的情況,下面我們加上同步鎖

     (1)用NSLock加同步鎖,代碼如下:

1     //通過NSLock加鎖
2     [_lock lock];
3     _count ++;
4     [NSThread sleepForTimeInterval:1];
5     int count = _count;
6     [_lock unlock];

    (2)通過@synchronized加同步鎖,代碼如下:

1     //通過synchronized加鎖
2     int count;
3     @synchronized(self){
4         _count ++;
5         [NSThread sleepForTimeInterval:1];
6          count = _count;
7     }

      加鎖前后的運行效果如下:

  GCD的串行隊列開始執行的順序如下,下面是是在一個線程中按FIFO的順序執行的:

  GCD中的並行隊列,是在不同的線程中同時執行的:

 

  今天博客中的內容還是蠻多的,如果之前接觸過Java的多線程的東西,或者其他語言中的多線程的話,理解起來應該問題不大。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM