GCD的高級(jí)用法

一次性執(zhí)行(dispatch_once_t)

實(shí)際開發(fā)中有時(shí)我們需要某個(gè)方法只執(zhí)行一次颈抚,而且要保證線程是安全的锯厢,那么dispatch_once_t無疑是很好的選擇枯冈。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self onceTest];
}

- (void)onceTest {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"%@", [NSThread currentThread]);
    });
}

執(zhí)行在主線程上

<NSThread: 0x60800006b0c0>{number = 1, name = main}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self onceTest];
    });
}

執(zhí)行在當(dāng)前線程上

<NSThread: 0x608000263080>{number = 3, name = (null)}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    for(int i = 0; i< 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self onceTest];
        });
    }
}

線程是安全的茵瀑,同時(shí)開啟多個(gè)任務(wù)執(zhí)行once仍然只執(zhí)行一次

<NSThread: 0x608000266040>{number = 3, name = (null)}

實(shí)際開發(fā)中once最常用在創(chuàng)建單例的對(duì)象上


#import <Foundation/Foundation.h>

@interface JYPerson : NSObject <NSCopying>

+ (instancetype)sharedPerson;

@end


#import "JYPerson.h"

@implementation JYPerson

// 定義一個(gè)靜態(tài)變量,保證內(nèi)存中只有一個(gè)副本惜犀,而且是保存在靜態(tài)區(qū)直到程序退出才銷毀
static id instance;

// 提供一個(gè)全局的訪問方法(補(bǔ)充:有一個(gè)約定铛碑,所有的單例都是以 shared + 類名 格式定義)
+ (instancetype)sharedPerson {
    // 用dispatch_once的目的:保證對(duì)象只會(huì)被初始化(init)一次,切線程是安全的
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    
    return instance;
}

// 重寫allocWihtZone方法保證虽界,對(duì)象只會(huì)被實(shí)例化一次汽烦,只分配一次內(nèi)存空間
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    // 用dispatch_once的目的:在多線程運(yùn)行在保證allocWithZone只調(diào)用一次,即內(nèi)存只分配一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [super allocWithZone:zone];
    });
    return instance;
}

- (id)copyWithZone:(NSZone *)zone {
    //    return [[self.class allocWithZone:zone] init];
    // 對(duì)象方法莉御,說明拷貝的源已經(jīng)存在 => instance 就一定已經(jīng)被實(shí)例化過撇吞,所以這里直接返回實(shí)例instance
    return instance;
}

@end

順便介紹下Swift中單利寫法(dispatch_once_t在swift中被廢棄了)

import UIKit

class JYPerson: NSObject {
    static let shareInstance: JYPerson = {
        return JYPerson()
    }()
}

延時(shí)操作

// OC
/**
參數(shù)1: dispatch_time_t when
多少納秒之后執(zhí)行

參數(shù)2: dispatch_queue_t queue
執(zhí)行任務(wù)的隊(duì)列

 參數(shù)3: dispatch_block_t block
要執(zhí)行的任務(wù)
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<delayInSeconds> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 要執(zhí)行的任務(wù)
    });

// Swift
func test2() -> () {
        // 原先的dispatch_time_t現(xiàn)在由DispatchTime對(duì)象表示〗甘澹可以用靜態(tài)方法now獲得當(dāng)前時(shí)間梢夯,然后再通過加上一個(gè)。DispatchTimeInterval枚舉來獲得一個(gè)需要延遲的時(shí)間
        print("操作一")
        
        // let delay = DispatchTime.now() + DispatchTimeInterval.seconds(3)
        // 這里也可以直接加上一個(gè)秒數(shù)
        let delay = DispatchTime.now() + 3
        DispatchQueue.main.asyncAfter(deadline: delay) {
            print("操作二:延遲了3s執(zhí)行")
        }
    }

調(diào)度組(Dispatch_groups)

Dispatch Group 會(huì)在整個(gè)組的任務(wù)都完成時(shí)發(fā)出通知晴圾,這些任務(wù)可以是同步的,也可以是異步的噪奄,即便在不同的隊(duì)列也行死姚。對(duì)多個(gè)異步任務(wù)的完成進(jìn)行監(jiān)控的問題,這無疑是一個(gè)非常好的選擇勤篮。
因?yàn)楸槐O(jiān)控的任務(wù)可能在不同的隊(duì)列都毒,因此用一個(gè) dispatch_group_t 的實(shí)例來記下這些不同的任務(wù)。
當(dāng)組中所有的事件都完成時(shí)碰缔,GCD 的 API 提供了兩種通知方式账劲。

用法一

利用 dispatch_group_wait ,它會(huì)阻塞當(dāng)前線程金抡,直到組里面所有的任務(wù)都完成或者等到某個(gè)超時(shí)發(fā)生瀑焦。
因?yàn)槟阍谑褂玫氖峭降?dispatch_group_wait ,它會(huì)阻塞當(dāng)前線程梗肝,所以你要用 dispatch_async 將整個(gè)方法放入后臺(tái)隊(duì)列以避免阻塞主線程榛瓮。

// OC
- (void)groupTest {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 創(chuàng)建一個(gè)dispatch_group
        dispatch_group_t group = dispatch_group_create();
        
        // 手動(dòng)通知進(jìn)入dispatch_group_t(你必須保證 dispatch_group_enter 和 dispatch_group_leave 成對(duì)出現(xiàn),否則你可能會(huì)遇到詭異的崩潰問題)
        dispatch_group_enter(group);
        
        NSLog(@"任務(wù)1");
        [NSThread sleepForTimeInterval:3.0];
        NSLog(@"任務(wù)1");
        NSLog(@"任務(wù)1");
        
        // 手動(dòng)通知結(jié)束dispatch_group_t
        dispatch_group_leave(group);
        
        // 一直等待直到dispatch_group_t中所有任務(wù)執(zhí)行完畢
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"拉回主線程刷新UI");
        });
    });
}

// Swift
 func groupTest() -> () {
        DispatchQueue.global().async {
            // 創(chuàng)建一個(gè)dispatch_group
            let group = DispatchGroup();
            
            // 手動(dòng)通知進(jìn)入dispatch_group_t(你必須保證 dispatch_group_enter 和 dispatch_group_leave 成對(duì)出現(xiàn)巫击,否則你可能會(huì)遇到詭異的崩潰問題)
            group.enter()
            
            print("任務(wù)1")
            print("任務(wù)2")
            print("任務(wù)3")
            
            group.leave()
            
            // 一直等待直到dispatch_group_t中所有任務(wù)執(zhí)行完畢
            group.wait()
            
            DispatchQueue.main.async(execute: {
                print("拉回主線程刷新UI")
            })
        }
    }

控制臺(tái)輸出結(jié)果

[1670:108365] 任務(wù)1
[1670:108365] 任務(wù)2
[1670:108365] 任務(wù)3
[1670:108304] 拉回主線程刷新UI

基本實(shí)現(xiàn)過程:
創(chuàng)建一個(gè)group 禀晓,執(zhí)行g(shù)roupenter 和 leave 中間的代碼 精续,然后再利用dispatch_group_wait 讓線程阻塞在這里 一直等待,最后再去執(zhí)行刷新UI或者處理group結(jié)果的部分

用法二

然而粹懒,用阻塞線程這種方法顯得不是那么友好 重付,我們來看看第二種 ,不阻塞線程的方法:dispatch_group_notify

// OC
- (void)groupTest {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 創(chuàng)建一個(gè)dispatch_group
        dispatch_group_t group = dispatch_group_create();
        
        // 手動(dòng)通知進(jìn)入dispatch_group_t(你必須保證 dispatch_group_enter 和 dispatch_group_leave 成對(duì)出現(xiàn)凫乖,否則你可能會(huì)遇到詭異的崩潰問題)
        dispatch_group_enter(group);
        
        NSLog(@"任務(wù)1");
        [NSThread sleepForTimeInterval:3.0];
        NSLog(@"任務(wù)2");
        NSLog(@"任務(wù)3");
        
        // 手動(dòng)通知結(jié)束dispatch_group_t
        dispatch_group_leave(group);
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"拉回主線程刷新UI");
        });
    });
}

// Swift
func groupTest() -> () {
        DispatchQueue.global().async {
            // 創(chuàng)建一個(gè)dispatch_group
            let group = DispatchGroup();
            
            // 手動(dòng)通知進(jìn)入dispatch_group_t(你必須保證 dispatch_group_enter 和 dispatch_group_leave 成對(duì)出現(xiàn)确垫,否則你可能會(huì)遇到詭異的崩潰問題)
            group.enter()
            
            print("任務(wù)1")
            print("任務(wù)2")
            print("任務(wù)3")
            
            group.leave()
            
            let workItem = DispatchWorkItem(block: {
                print("拉回主線程刷新UI")
            })
            group.notify(queue: DispatchQueue.main, work: workItem)
        }
    }

控制臺(tái)輸出結(jié)果

[1670:108365] 任務(wù)1
[1670:108365] 任務(wù)2
[1670:108365] 任務(wù)3
[1670:108304] 拉回主線程刷新UI

用法三(平時(shí)開發(fā)最常用)
// OC
- (void)groupTest {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 創(chuàng)建一個(gè)dispatch_group
        dispatch_group_t group = dispatch_group_create();
        
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            NSLog(@"任務(wù)1");
        });
        
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            NSLog(@"任務(wù)2");
        });

        dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
            NSLog(@"任務(wù)3");
        });
    });
}

// Swift
func groupTest() -> () {
        DispatchQueue.global().async {
            // 創(chuàng)建一個(gè)dispatch_group
            let group = DispatchGroup()
            
            let queueOne = DispatchQueue(label: "queueOne")
            queueOne.async(group: group) {
                print("任務(wù)1")
            }
            
            let queueTwo = DispatchQueue(label: "queueTwo")
            queueTwo.async(group: group) {
                print("任務(wù)2")
            }
            
            group.notify(queue: DispatchQueue.main) {
                print("任務(wù)3")
            }
        }
    }

控制臺(tái)輸出結(jié)果

任務(wù)1
任務(wù)2
任務(wù)3
或者
任務(wù)2
任務(wù)1
任務(wù)3

任務(wù)1和任務(wù)2是異步的所以先后順序不可控,任務(wù)3只有在group中所有任務(wù)執(zhí)行完畢調(diào)用

dispatch_barrier

在并行隊(duì)列中拣凹,為了保持某些任務(wù)的順序森爽,需要等待一些任務(wù)完成后才能繼續(xù)進(jìn)行,使用 barrier 來等待之前任務(wù)完成嚣镜,避免數(shù)據(jù)競(jìng)爭(zhēng)等問題爬迟。假設(shè)我們有一個(gè)并發(fā)的隊(duì)列用來讀寫一個(gè)數(shù)據(jù)對(duì)象。如果這個(gè)隊(duì)列里的操作是讀的菊匿,那么可以多個(gè)同時(shí)進(jìn)行付呕。如果有寫的操作,則必須保證在執(zhí)行寫入操作時(shí)跌捆,不會(huì)有讀取操作在執(zhí)行徽职,必須等待寫入完成后才能讀取,否則就可能會(huì)出現(xiàn)讀到的數(shù)據(jù)不對(duì)佩厚。OC用dipatch_barrier實(shí)現(xiàn)姆钉。Swift中用DispatchWorkItemFlags實(shí)現(xiàn)。

// OC
- (void)barrierTest {
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)1");
    });
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)2");
        [NSThread sleepForTimeInterval:2.0];
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"barrier測(cè)試");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)3");
    });
}

// Swift
func barrierTest() -> () {
        let queue = DispatchQueue(label: "queue", attributes: .concurrent)
        
        queue.async { 
            print("任務(wù)1")
        }
        queue.async {
            print("任務(wù)2")
            Thread.sleep(forTimeInterval: 2.0)
        }
        
        let barrierWorkItm = DispatchWorkItem(flags: .barrier) {
            print("barrier測(cè)試")
        }
        queue.async(execute: barrierWorkItm)
        
        queue.async {
            print("任務(wù)3")
        }
    }

控制臺(tái)輸出結(jié)果

任務(wù)1
任務(wù)2
barrier測(cè)試
任務(wù)3

任務(wù)2
任務(wù)1
barrier測(cè)試
任務(wù)3

任務(wù)1和任務(wù)2是異步的所以先后順序不可控抄瓦,barrier會(huì)等待當(dāng)前隊(duì)列中先于自己添加的任務(wù)完成再執(zhí)行潮瓶,并阻塞當(dāng)前線程,等待barrier函數(shù)執(zhí)行完畢當(dāng)前線程才恢復(fù)之前的動(dòng)作繼續(xù)執(zhí)行钙姊,任務(wù)3只barrier函數(shù)執(zhí)行完畢后才能調(diào)用毯辅。

注意:使用 dispatch_barrier_async ,該函數(shù)只能搭配自定義并行隊(duì)列 dispatch_queue_t 使用煞额。不能使用: dispatch_get_global_queue 思恐,否則 dispatch_barrier_async 的作用會(huì)和 dispatch_async 的作用一模一樣。

dispatch_apply

作用:按指定的次數(shù)將指定的Block追加到指定的Dispatch Queue中,并等到全部的處理執(zhí)行結(jié)束(類似for循環(huán))

并行隊(duì)列
- (void)apply {
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(5, queue, ^(size_t index) {
        NSLog(@"當(dāng)前線程:%@, index: %zu",[NSThread currentThread], (index + 1));
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"done");
    });
}

控制臺(tái)輸出結(jié)果:并行隊(duì)列上是并發(fā)執(zhí)行的膊毁,但并非所有任務(wù)都開辟新線程胀莹,也有在主線程中完成的。dispatch_apply會(huì)阻塞線程媚媒,直到dispatch_apply函數(shù)執(zhí)行完畢才繼續(xù)往下走嗜逻,所有done最后打印

當(dāng)前線程:<NSThread: 0x600000075ac0>{number = 4, name = (null)}, index: 3
當(dāng)前線程:<NSThread: 0x60800006a9c0>{number = 1, name = main}, index: 1
當(dāng)前線程:<NSThread: 0x608000078440>{number = 5, name = (null)}, index: 4
當(dāng)前線程:<NSThread: 0x608000078640>{number = 3, name = (null)}, index: 2
當(dāng)前線程:<NSThread: 0x600000075ac0>{number = 4, name = (null)}, index: 5
done

串行隊(duì)列
- (void)applyTest {
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    dispatch_apply(5, queue, ^(size_t index) {
        NSLog(@"當(dāng)前線程:%@, index: %zu",[NSThread currentThread], (index + 1));
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"done");
    });
}

控制臺(tái)輸出結(jié)果:

當(dāng)前線程:<NSThread: 0x600000075ac0>{number = 4, name = (null)}, index: 1
當(dāng)前線程:<NSThread: 0x60800006a9c0>{number = 1, name = main}, index: 2
當(dāng)前線程:<NSThread: 0x608000078440>{number = 5, name = (null)}, index: 3
當(dāng)前線程:<NSThread: 0x608000078640>{number = 3, name = (null)}, index: 4
當(dāng)前線程:<NSThread: 0x600000075ac0>{number = 4, name = (null)}, index: 5
done

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市缭召,隨后出現(xiàn)的幾起案子栈顷,更是在濱河造成了極大的恐慌逆日,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萄凤,死亡現(xiàn)場(chǎng)離奇詭異室抽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)靡努,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門坪圾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人惑朦,你說我怎么就攤上這事兽泄。” “怎么了漾月?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵病梢,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我梁肿,道長(zhǎng)蜓陌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任吩蔑,我火速辦了婚禮钮热,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘烛芬。我一直安慰自己隧期,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布赘娄。 她就那樣靜靜地躺著厌秒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪擅憔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天檐晕,我揣著相機(jī)與錄音暑诸,去河邊找鬼。 笑死辟灰,一個(gè)胖子當(dāng)著我的面吹牛个榕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芥喇,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼西采,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了继控?” 一聲冷哼從身側(cè)響起械馆,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤胖眷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后霹崎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體珊搀,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年尾菇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了境析。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡派诬,死狀恐怖劳淆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情默赂,我是刑警寧澤沛鸵,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站放可,受9級(jí)特大地震影響谒臼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜耀里,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一蜈缤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冯挎,春花似錦底哥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至翰守,卻和暖如春孵奶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜡峰。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工了袁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人湿颅。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓载绿,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親油航。 傳聞我的和親對(duì)象是個(gè)殘疾皇子崭庸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容