iOS - GCD編程

今天溫故了一下有關(guān) GCD 編程的相關(guān)知識(shí),做了個(gè)小Demo,梳理一下。其中也是用到在github上下載的別的大牛封裝好的一個(gè)GCD源代碼,使用很方便,另外里面也用到了SDWebImage這個(gè)第三方庫,設(shè)置網(wǎng)絡(luò)圖片

Demo的源代碼放在 Coding.net 上,有需要可以下載查看。

主要內(nèi)容

  • GCD串行隊(duì)列與并發(fā)隊(duì)列
  • GCD延時(shí)執(zhí)行
  • GCD線程組
  • GCD定時(shí)器
  • GCD信號(hào)量
  • GCD綜合使用示例

GCD串行隊(duì)列與并發(fā)隊(duì)列

  • 串行隊(duì)列:串行隊(duì)列一次只執(zhí)行一個(gè)線程,按照添加到隊(duì)列的順序依次執(zhí)行
  • 并發(fā)隊(duì)列: 一次可以執(zhí)行多個(gè)線程,線程之間沒有先后順序
  • UI界面所在線程是串行隊(duì)列

首先創(chuàng)建串行隊(duì)列

// 創(chuàng)建串行隊(duì)列
- (void)serailQueue {
    // 創(chuàng)建隊(duì)列
    GCDQueue *queue = [[GCDQueue alloc] initSerial];
    //執(zhí)行隊(duì)列中的線程1
    [queue execute:^{
        NSLog(@"線程1");
    }];

    //執(zhí)行隊(duì)列中的線程2
    [queue execute:^{
        NSLog(@"線程2");
    }];

    //執(zhí)行隊(duì)列中的線程3
    [queue execute:^{
        NSLog(@"線程3");
    }];

    //執(zhí)行隊(duì)列中的線程4
    [queue execute:^{
        NSLog(@"線程4");
    }];

    //執(zhí)行隊(duì)列中的線程5
    [queue execute:^{
        NSLog(@"線程5");
    }];
}

viewDidLoad里調(diào)用該方法

    // 執(zhí)行串行隊(duì)列
    [self serailQueue];

輸出結(jié)果能看出,線程執(zhí)行是按順序依次執(zhí)行的


輸出結(jié)果

再看并發(fā)隊(duì)列

// 創(chuàng)建并發(fā)隊(duì)列
- (void)initConcurrent {
    // 創(chuàng)建隊(duì)列
    GCDQueue *queue = [[GCDQueue alloc] initConcurrent];
    //執(zhí)行隊(duì)列中的線程1
    [queue execute:^{
        NSLog(@"線程1");
    }];

    //執(zhí)行隊(duì)列中的線程2
    [queue execute:^{
        NSLog(@"線程2");
    }];

    //執(zhí)行隊(duì)列中的線程3
    [queue execute:^{
        NSLog(@"線程3");
    }];

    //執(zhí)行隊(duì)列中的線程4
    [queue execute:^{
        NSLog(@"線程4");
    }];

    //執(zhí)行隊(duì)列中的線程5
    [queue execute:^{
        NSLog(@"線程5");
    }];
}

viewDidLoad里調(diào)用該方法

      // 執(zhí)行并發(fā)隊(duì)列
    [self initConcurrent];

此時(shí)的輸出結(jié)果就不再遵循有序執(zhí)行了,如下圖:


輸出結(jié)果

UI界面所在的線程是串行隊(duì)列

這個(gè)知識(shí)點(diǎn),也通過寫一段代碼來加載一張網(wǎng)絡(luò)圖片來演示
首先,聲明兩個(gè)屬性

@interface ViewController ()
// GCD 執(zhí)行隊(duì)列與UI界面所在線程隊(duì)列
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIImage     *image;

@end

然后寫一個(gè) UI界面所在的線程是串行隊(duì)列的方法

- (void)UIQueue {
    self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
    self.imageView.center = self.view.center;
    [self.view addSubview:self.imageView];

    [GCDQueue executeInGlobalQueue:^{
        // 處理業(yè)務(wù)邏輯
        // 網(wǎng)絡(luò)測(cè)試圖片url
        NSURL *imgUrl  = [NSURL URLWithString:@"http://o7mwf03sy.bkt.clouddn.com/1460334144112.jpg"];
        // 處理義務(wù)邏輯
        NSLog(@"處理業(yè)務(wù)邏輯");

        [GCDQueue executeInMainQueue:^{
            // 獲取圖片更新UI
            [self.imageView sd_setImageWithURL:imgUrl];
            // 更新UI
            NSLog(@"更新UI");  
        }];
    }];
}

控制臺(tái)輸出和模擬器效果如下:


結(jié)果

從結(jié)果我們也不難看出,UI所在的線程隊(duì)列是串行隊(duì)列。 所以有個(gè)注意的地方就是,在處理業(yè)務(wù)邏輯的地方,如果耗費(fèi)的資源和時(shí)間較多,就會(huì)阻塞主線程的執(zhí)行。

GCD延時(shí)執(zhí)行

在測(cè)試 GCD延時(shí)執(zhí)行 執(zhí)行前面,我們先來看一下 普通的 NSThread 延時(shí)執(zhí)行 效果:
先寫一個(gè)方法:

- (void)setNSThread {
    NSLog(@"啟動(dòng)");
   [self performSelector:@selector(threadEvent:)
              withObject:self
              afterDelay:2.f]; // 延時(shí)2秒執(zhí)行
}
// NSThread 延時(shí)執(zhí)行的事件
- (void)threadEvent:(id)sender {
    NSLog(@"NSThread 延時(shí)執(zhí)行事件");
}

NSThread延時(shí)執(zhí)行不僅要寫方法,還要寫一個(gè)執(zhí)行事件的方法,調(diào)用方法運(yùn)行結(jié)果如下:

結(jié)果

正好延時(shí)了2秒, 再來看 GCD延時(shí)執(zhí)行 方法,代碼相對(duì)來說比較簡(jiǎn)單

- (void)setGCDEvent {
     NSLog(@"啟動(dòng)");

    [GCDQueue executeInMainQueue:^{
        NSLog(@"GCD 延時(shí)執(zhí)行事件");
    } afterDelaySecs:2.f];
}

不需要額外的聲明方法,調(diào)用方法后運(yùn)行的結(jié)果是這樣的:


結(jié)果

也能達(dá)到我們要的延遲效果

這里之前有個(gè)誤區(qū), 簡(jiǎn)單看打印時(shí)間覺得 NSTimer 延時(shí)執(zhí)行任務(wù)是比較精準(zhǔn)的,但最近查了一些資料,其實(shí) NSTimer 是不精準(zhǔn)的, 因?yàn)樗皇且粋€(gè)實(shí)時(shí)系統(tǒng), 如果此時(shí)程序是多線程的, 而 NSTimer 又處在其中一個(gè) runloop 中的某一特定 Mode 中,由于多線程都是分時(shí)執(zhí)行的, 所以當(dāng) NSTimer 需要執(zhí)行任務(wù)的時(shí)候,很有可能當(dāng)前線程還在執(zhí)行任務(wù),此時(shí) NSTimer 則會(huì)等待其執(zhí)行完畢才會(huì)執(zhí)行延時(shí)任務(wù),所以說,NSTimer 是不精準(zhǔn)的.

除此之外, GCD延遲執(zhí)行 還可以取消延時(shí)事件的執(zhí)行,只需在執(zhí)行延時(shí)執(zhí)行前加一行代碼

[NSObject cancelPreviousPerformRequestsWithTarget:self];

NSTimer 沒有這個(gè)方法。

GCD線程組

在開發(fā)中,我們可能會(huì)有這樣的要求:就是在執(zhí)行完畢線程1和線程2之后,才去執(zhí)行線程3。有一種方法是設(shè)置一個(gè)標(biāo)志,線程1執(zhí)行完了標(biāo)志為1,線程2執(zhí)行完了再+1變成2,線程3執(zhí)行的時(shí)候先判斷當(dāng)前標(biāo)志的值是多少,再來決定是否執(zhí)行,貌似這種方法是可行的,但總感覺是不科學(xué)的,我們完全可以用GCD線程組來完成這個(gè)需求;
首先聲明方法

- (void)setGCDGroup {
    // 初始化線程組
    GCDGroup *group = [[GCDGroup alloc] init];

    // 創(chuàng)建一個(gè)線程隊(duì)列
    GCDQueue *queue = [[GCDQueue alloc] initConcurrent];

    // 讓線程在group中執(zhí)行 線程1
    [queue execute:^{
        sleep(1);// 休眠
        NSLog(@"線程1執(zhí)行完畢");
    } inGroup:group];

    // 讓線程在group中執(zhí)行 線程2
    [queue execute:^{
        sleep(3);
        NSLog(@"線程2執(zhí)行完畢");
    } inGroup:group];

    // 監(jiān)聽線程組是否執(zhí)行完畢,然后執(zhí)行線程3
    [queue notify:^{
        NSLog(@"線程3執(zhí)行完畢");
    } inGroup:group];
}

為了展示效果,這里讓線程1休眠1秒執(zhí)行,線程2休眠3秒執(zhí)行(也就是在線程1之后2秒執(zhí)行),看看是否是我們想要的結(jié)果。


結(jié)果

可見,GCD 線程組 實(shí)現(xiàn)了我們想要的結(jié)果.線程組的作用一般就是實(shí)現(xiàn)監(jiān)聽響應(yīng)的線程執(zhí)行完畢后才執(zhí)行。

GCDTimer 定時(shí)器

同樣,為了展示效果,我們還是先來看看NSTimer 的定時(shí)執(zhí)行效果
首先,聲明兩個(gè)私有屬性

@property (nonatomic, strong) GCDTimer *gcdTimer;
@property (nonatomic, strong) NSTimer  *normalTimer;

接著,創(chuàng)建 NSTimer方法

- (void)runNSTimer {
    // 初始化并激活NSTimer
    self.normalTimer = [NSTimer scheduledTimerWithTimeInterval:1
                                                        target:self
                                                      selector:@selector(timerEvent)
                                                      userInfo:nil
                                                       repeats:YES];
}
// NSTimer 執(zhí)行時(shí)間
- (void)timerEvent {
    NSLog(@"運(yùn)行NSTimer");
}

調(diào)用該方法執(zhí)行結(jié)果為


結(jié)果

再來看GCDTimer
聲明方法:

- (void)runGCDTimer {
    // 初始化GCDTimer
    self.gcdTimer = [[GCDTimer alloc] initInQueue:[GCDQueue mainQueue]];

    // 指定間隔時(shí)間
    [self.gcdTimer event:^{
        NSLog(@"運(yùn)行GCDTimer");
    } timeInterval:NSEC_PER_SEC];// NSEC_PER_SEC 宏 代表1 秒

    // 運(yùn)行GCDTimer
    [self.gcdTimer start];
}

調(diào)用該方法執(zhí)行結(jié)果為


結(jié)果

可以看到,GCDTimer 雖然在打印時(shí)間上有那么一點(diǎn)點(diǎn)誤差, 但其實(shí) GCD 的延時(shí)操作才是最為精準(zhǔn)的,因?yàn)?code>NSTimer是運(yùn)行在Runloop里面的,而 RunLoop 是在 GCD 基礎(chǔ)上實(shí)現(xiàn)的,所以說 GCD 可算是更加精準(zhǔn). 并且如果把NSTimer運(yùn)行在一些例如UITableView里面,可能會(huì)出現(xiàn)一些奇怪的問題,而GCDTimer不存在這個(gè)問題。

GCDSemaphore (GCD信號(hào)量)

先來看看我們正常調(diào)用一個(gè)異步線程方法的輸出結(jié)果

- (void)setGCDSemaphore {
    // 異步線程 1
    [GCDQueue executeInGlobalQueue:^{
        NSLog(@"異步線程 1");
    }];

    // 異步線程 2
    [GCDQueue executeInGlobalQueue:^{  
        NSLog(@"異步線程 2");
    }];
}

在異步線程執(zhí)行里,我們是無法確定是哪個(gè)線程先執(zhí)行完畢的,從輸出結(jié)果就能看出,第一次是這樣的:


結(jié)果

第二次是這樣的:


結(jié)果

第三次是這樣的:


結(jié)果

、、、、、
以此可見,它的執(zhí)行順序是不固定的,這個(gè)時(shí)候,如果使用GCDSemaphore,就能按著我們的意愿順序執(zhí)行線程了。
創(chuàng)建GCDSemaphore并在響應(yīng)位置加入消息指令:

- (void)setGCDSemaphore {
    // 創(chuàng)建GCDSemaphore 信號(hào)量
    GCDSemaphore *semaphore = [[GCDSemaphore alloc] init];

    // 異步線程 1
    [GCDQueue executeInGlobalQueue:^{
        NSLog(@"異步線程 1");

        // 發(fā)送執(zhí)行完畢信號(hào)
        [semaphore signal];
    }];

    // 異步線程 2
    [GCDQueue executeInGlobalQueue:^{
        // 等待接收?qǐng)?zhí)行完畢信號(hào)才開始執(zhí)行進(jìn)程
        [semaphore wait];

        NSLog(@"異步線程 2");
    }];
    // 作用: 必須線程1先執(zhí)行完畢,然后在執(zhí)行線程2
    // 將異步線程轉(zhuǎn)化成同步線程。
}

這樣,每次輸出順序就是一樣的

結(jié)果

這樣,當(dāng)我們有這種按順序執(zhí)行的特殊需求時(shí),GCDSemaphore就可以發(fā)揮很好的作用了。

GCD 綜合使用介紹示例

這里我們通過并發(fā)下載三張網(wǎng)絡(luò)圖片的例子來延時(shí) GCD 的強(qiáng)大作用。首先生命三個(gè)私有屬性

@property (nonatomic, strong) UIImageView *view1;
@property (nonatomic, strong) UIImageView *view2;
@property (nonatomic, strong) UIImageView *view3;

為了等下代碼更加方便操作,簡(jiǎn)化代碼,我們聲明一個(gè)設(shè)置view的方法,這里同樣用到SDWebImage獲取圖片。

// 創(chuàng)建 imageview ,需要frame 和 圖片網(wǎng)絡(luò)地址 的參數(shù)
- (UIImageView *)createImageViewWithFrame:(CGRect)frame imageUreStr:(NSString *)string {
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
    imageView.alpha = 0.f; // 透明度設(shè)置為0
    [imageView sd_setImageWithURL:[NSURL URLWithString:string]];
    [self.view addSubview:imageView];

    return imageView;
}

接著我們創(chuàng)建一個(gè)方法,就叫 setGCD 吧。

- (void)setGCD {

    NSString *img1 = @"http://o7mwf03sy.bkt.clouddn.com/1460334156212.jpg";
    NSString *img2 = @"http://o7mwf03sy.bkt.clouddn.com/1460334134611.jpg";
    NSString *img3 = @"http://o7mwf03sy.bkt.clouddn.com/1460334144112.jpg";

    self.view1 = [self createImageViewWithFrame:CGRectMake(50, 50, 200, 100) imageUreStr:img1];
    self.view2 = [self createImageViewWithFrame:CGRectMake(50, 200, 200, 100) imageUreStr:img2];
    self.view3 = [self createImageViewWithFrame:CGRectMake(50, 350, 200, 100) imageUreStr:img3];

    // 在子線程中完成下載  圖片1
    [GCDQueue executeInGlobalQueue:^{
        // 在主線程中更新UI
        [GCDQueue executeInMainQueue:^{
          NSLog(@"線程 1 開始執(zhí)行");
            // 2秒動(dòng)畫顯示圖片
            [UIView animateWithDuration:2.f animations:^{
                self.view1.alpha = 1.f;
            } completion:^(BOOL finished) {
              NSLog(@"線程 1 執(zhí)行完畢");
            }];
        }];
    }];

    // 在子線程中完成下載  圖片2
    [GCDQueue executeInGlobalQueue:^{
      NSLog(@"線程 2 開始執(zhí)行");
        // 在主線程中更新UI
        [GCDQueue executeInMainQueue:^{
            // 2秒動(dòng)畫顯示圖片
            [UIView animateWithDuration:2.f animations:^{
                self.view2.alpha = 1.f;
            } completion:^(BOOL finished) {
              NSLog(@"線程 2 執(zhí)行完畢");
            }];
        }];
    }];

    // 在子線程中完成下載  圖片3
    [GCDQueue executeInGlobalQueue:^{
      NSLog(@"線程 3 開始執(zhí)行");
        // 在主線程中更新UI
        [GCDQueue executeInMainQueue:^{
            // 2秒動(dòng)畫顯示圖片
            [UIView animateWithDuration:2.f animations:^{
                self.view3.alpha = 1.f;
            } completion:^(BOOL finished) {
                NSLog(@"線程 3 執(zhí)行完畢");
            }];
        }];
    }];
}

此時(shí)的輸出結(jié)果是:

輸出結(jié)果

可見三張圖片幾乎是同時(shí)開始執(zhí)行, 同時(shí)執(zhí)行完畢。 但是假如我們有一種需求,要三張圖片按順序一張一張的出現(xiàn),該怎么辦呢?
對(duì)了,前面介紹過,使用GCDSemaphore信號(hào)量!創(chuàng)建 GCDSemaphore 并在響應(yīng)位置插入信號(hào)代碼

...
self.view3 = [self createImageViewWithFrame:CGRectMake(50, 350, 200, 100) imageUreStr:img3];

   GCDSemaphore *semaphore = [[GCDSemaphore alloc] init];

   // 在子線程中完成下載  圖片1
   [GCDQueue executeInGlobalQueue:^{
       // 在主線程中更新UI
       [GCDQueue executeInMainQueue:^{
           NSLog(@"線程 1 開始執(zhí)行");
           // 2秒動(dòng)畫顯示圖片
           [UIView animateWithDuration:2.f animations:^{
               self.view1.alpha = 1.f;
           } completion:^(BOOL finished) {
               NSLog(@"線程 1 執(zhí)行完畢");
               // 發(fā)送執(zhí)行完畢信號(hào)
               [semaphore signal];
           }];
       }];
   }];

   // 在子線程中完成下載  圖片2
   [GCDQueue executeInGlobalQueue:^{
       // 阻塞執(zhí)行,等待消息
       [semaphore wait];
       NSLog(@"線程 2 開始執(zhí)行");
       // 在主線程中更新UI
       [GCDQueue executeInMainQueue:^{
           // 2秒動(dòng)畫顯示圖片
           [UIView animateWithDuration:2.f animations:^{
               self.view2.alpha = 1.f;
           } completion:^(BOOL finished) {
               NSLog(@"線程 2 執(zhí)行完畢");
               // 發(fā)送執(zhí)行完畢信號(hào)
               [semaphore signal];
           }];
       }];
   }];

   // 在子線程中完成下載  圖片3
   [GCDQueue executeInGlobalQueue:^{
       // 阻塞執(zhí)行,等待消息
       [semaphore wait];
       NSLog(@"線程 3 開始執(zhí)行");
       // 在主線程中更新UI
       [GCDQueue executeInMainQueue:^{
           // 2秒動(dòng)畫顯示圖片
           [UIView animateWithDuration:2.f animations:^{
               self.view3.alpha = 1.f;
           } completion:^(BOOL finished) {
               NSLog(@"線程 3 執(zhí)行完畢");
           }];
       }];
   }];
}

這樣,在線程1執(zhí)行完畢后發(fā)送信號(hào),線程2接收線程1發(fā)送的信號(hào)再開始執(zhí)行,完畢之后再給線程3發(fā)送信號(hào),以此類推,就能實(shí)現(xiàn)按順序執(zhí)行線程。輸出結(jié)果如下:


結(jié)果

三個(gè)線程的開始執(zhí)行和結(jié)束執(zhí)行的時(shí)間有了明顯差別,實(shí)現(xiàn)了我們想要的效果。

總結(jié)

到這里,我們基本能學(xué)到一下幾點(diǎn)有關(guān)GCD的實(shí)用知識(shí)點(diǎn)

  • GCD串行隊(duì)列與并發(fā)隊(duì)列
  • GCD延時(shí)執(zhí)行操作使用
  • GCD線程組的操作使用
  • GCD定時(shí)器的使用
  • GCD信號(hào)量將異步操作轉(zhuǎn)變成同步操作的使用
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從哪說起呢? 單純講多線程編程真的不知道從哪下嘴。。 不如我直接引用一個(gè)最簡(jiǎn)單的問題,以這個(gè)作為切入點(diǎn)好了 在ma...
    Mr_Baymax閱讀 2,912評(píng)論 1 17
  • 本文用來介紹 iOS 多線程中 GCD 的相關(guān)知識(shí)以及使用方法。這大概是史上最詳細(xì)、清晰的關(guān)于 GCD 的詳細(xì)講...
    花花世界的孤獨(dú)行者閱讀 579評(píng)論 0 1
  • 本文轉(zhuǎn)載自:行走的少年郎的簡(jiǎn)書:iOS多線程:『GCD』詳盡總結(jié) 本文用來介紹 iOS 多線程中 GCD 的相關(guān)知...
    遠(yuǎn)遊旳遊子閱讀 1,158評(píng)論 0 10
  • 01/12/2018 長(zhǎng)達(dá)30個(gè)小時(shí)沒有吃飯的我在吃到一碗排骨紫菜湯面的時(shí)候,感覺世界都是溫暖的。還有清蒸雞翅等著...
    陌路遇見閱讀 234評(píng)論 0 0
  • 一、這周的目標(biāo)達(dá)成情況 運(yùn)動(dòng)方面: 倒立5分40秒 未達(dá)成,客觀原因,這周只倒立一次,目前5分25秒。 長(zhǎng)跑49分...
    黃道凱閱讀 511評(píng)論 0 1

友情鏈接更多精彩內(nèi)容