任務(wù)就是需要代碼做的事,GCD負(fù)責(zé)提供完成任務(wù)的技巧;
GCD添加任務(wù)主要有兩種形式,一種是通過閉包,一種是創(chuàng)建DispatchWorkItem;
還有比較特別的DispatchSource這里先不提
一: DispatchWorkItem
DispatchWorkItem是把任務(wù)本體,加上優(yōu)先級(jí)和執(zhí)行策略封裝在一起.
并且優(yōu)先級(jí)和執(zhí)行策略可以有默認(rèn)值
1.構(gòu)造
DispatchQueue.main.async(execute: DispatchWorkItem.init(block: {
print("aaa")
}))
DispatchWorkItem.init(qos: .userInteractive, flags: .assignCurrentContext) {
}
優(yōu)先級(jí)qos上一篇已經(jīng)說了,這里說一下flags;
flags可以多選,默認(rèn)是[];
a. assignCurrentContext 這個(gè)選項(xiàng)會(huì)使任務(wù)使用所在隊(duì)列或者線程(或者說當(dāng)前執(zhí)行上下文)的屬性設(shè)置,比如優(yōu)先級(jí)
b. detached 這個(gè)選項(xiàng)是系統(tǒng)不會(huì)把當(dāng)前線程或者隊(duì)列的屬性設(shè)置應(yīng)用在這個(gè)任務(wù)上
c. enforceQoS 這個(gè)選項(xiàng)是將本任務(wù)的優(yōu)先級(jí)相對當(dāng)前上下文提升或者保持,總之不會(huì)降低
d. inheritQoS和上面相反,這個(gè)選項(xiàng)會(huì)會(huì)設(shè)置當(dāng)前任務(wù)的優(yōu)先級(jí)低于上下文,也就是可能會(huì)降低
e. noQoS 不指定優(yōu)先級(jí)
f. barrier 和OC的barrier類似,都是阻塞隊(duì)列,當(dāng)作用與并發(fā)隊(duì)列時(shí),后面添加的任務(wù)會(huì)等待這個(gè)任務(wù)執(zhí)行完畢后才開始.
let queue = DispatchQueue.init(label: "queue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
for index in 0 ..< 100 {
if index >= 10 && index < 20{
let work = DispatchWorkItem.init(qos: .default, flags: .barrier) {
print("barrier\(index)")
}
queue.async(execute: work)
}else{
queue.async {
print(index)
}
}
}
上面這段代碼展示了barrier如何使用;
創(chuàng)建了一個(gè)并發(fā)隊(duì)列queue,當(dāng)10到19時(shí),創(chuàng)建barrier的DispatchWorkItem,添加到隊(duì)列,而其他時(shí)候添加默認(rèn)flags的任務(wù);

可以看到barrier的任務(wù)會(huì)阻塞隊(duì)列,連在一起并且按順序輸出,并且都在同一個(gè)線程中,而其他的任務(wù)會(huì)分布在很多個(gè)線程中.
這個(gè)例子就是常用的解決異步讀寫的方法,10到19相當(dāng)于寫入,其他的是讀取;讀與讀之間可以異步進(jìn)行,但是寫和讀,寫和寫之間必須是同步的,否則就是線程不安全的.
如果把這個(gè)例子中的queue換成DispatchQueue.global(),不管使用什么優(yōu)先級(jí),都不能成功阻塞隊(duì)列,我猜想是系統(tǒng)提供的隊(duì)列不能隨便阻塞,畢竟里面可能還有系統(tǒng)的任務(wù)在,GCD會(huì)自己管理.
2.函數(shù)
- perform()
DispatchQueue.global().async {
print(Thread.current)
let work = DispatchWorkItem.init {
print("aaa -- \(Thread.current)")
}
work.perform()
}
perform()可以使任務(wù)直接在當(dāng)前的線程中執(zhí)行

- DispatchTime
DispatchTime是一個(gè)系統(tǒng)時(shí)間,通過各種函數(shù)來指定一個(gè)時(shí)長的值,但是這個(gè)時(shí)長不是數(shù)值類型,是一個(gè)結(jié)構(gòu)體
DispatchTime.now()
現(xiàn)在,也就是DispatchTime(系統(tǒng)時(shí)間)延后0秒這么一個(gè)時(shí)長,但是系統(tǒng)肯定做不到真正的0,也就是不能真正的瞬間執(zhí)行后面的事務(wù),只能盡量接近0
DispatchTime.distantFuture
這個(gè)函數(shù)是設(shè)置無限大的時(shí)長
運(yùn)算符函數(shù)
DispatchTime不是數(shù)值類型,不能直接比較和加減,因此apple專門提供了一套函數(shù),使DispatchTime可以和double加減比較或者和其他DispatchTime加減比較.

DispatchWallTime
DispatchWallTime是真實(shí)的系統(tǒng)時(shí)間,除此之外,和DispatchTime完全一致wait()
let work = DispatchWorkItem.init {
sleep(2)
print("working -- \(Thread.current)")
}
DispatchQueue.global().async(execute: work)
print("before -- \(Thread.current)")
work.wait()
print("after -- \(Thread.current)")
等待work完成之后,after才執(zhí)行;

這其實(shí)是一個(gè)線程間通信的效果,before和after都在主線程,而working在其他線程,wait添加在主線程,主線程就會(huì)等待另一個(gè)線程完成
let work = DispatchWorkItem.init {
sleep(2)
print("working -- \(Thread.current)")
}
DispatchQueue.global().sync(execute: work)
print("before -- \(Thread.current)")
work.wait()
print("after -- \(Thread.current)")
把上面例子的 DispatchQueue.global().async改成 DispatchQueue.global().sync,由于沒有開啟新線程,before上來就得等working執(zhí)行完,而且working執(zhí)行完之后wait也不再生效了,直接return

let work = DispatchWorkItem.init {
sleep(2)
print("working -- \(Thread.current)")
}
DispatchQueue.main.async(execute: work)
print("before -- \(Thread.current)")
work.wait()
print("after -- \(Thread.current)")

再改造一下,把work放在主隊(duì)列中異步執(zhí)行(同步會(huì)死鎖),這時(shí)working和after都不會(huì)執(zhí)行,只有before輸出了;
只要把DispatchWorkItem添加到主隊(duì)列,就不能使用wait,這會(huì)引起無限的等待.因?yàn)橹麝?duì)列的異步任務(wù)不會(huì)開啟新的線程,而是會(huì)等待主線程當(dāng)前的任務(wù)(UI,以及上面后幾行代碼)執(zhí)行完;
所以這里work還沒開始,就先wait了.
wait()可以指定等待時(shí)間,直接調(diào)用和使用wait(timeout:.distantFuture)是一樣的效果
let queue = DispatchQueue.init(label: "aaa")
let work = DispatchWorkItem.init {
sleep(1)
}
queue.async(execute: work)
let time = work.wait(timeout: .now() + 3)
if time == DispatchTimeoutResult.timedOut{
print("未完成")
}else if time == DispatchTimeoutResult.success{
print("已完成")
}
work.wait(timeout: .now() + 3)當(dāng)?shù)却龝r(shí)間到達(dá)時(shí),返回一個(gè)枚舉DispatchTimeoutResult;
如果是timedOut則任務(wù)還沒完成,如果是success則任務(wù)已經(jīng)完成
- Notify
func notify(queue: DispatchQueue, execute: DispatchWorkItem)
func notify(qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], queue: DispatchQueue, execute: @escaping () -> Void)
和wait一樣,也是為了線程間通信,可以在任務(wù)完成后,切換到另一個(gè)隊(duì)列,執(zhí)行下一個(gè)任務(wù)
let q = DispatchQueue.global()
let work = DispatchWorkItem.init(block: {
print("aaa -- \(Thread.current)")
sleep(2)
})
let q2 = DispatchQueue.init(label: "q2")
work.notify(queue: q2) {
print("bbb -- \(Thread.current)")
}
q.async(execute: work)
print("ccc -- \(Thread.current)")
這段代碼向隊(duì)列q中添加一個(gè)任務(wù)work(aaa),work設(shè)置notify為在執(zhí)行完之把任務(wù)bbb添加到隊(duì)列q2,然后異步執(zhí)行work;
當(dāng)work執(zhí)行完后,bbb會(huì)執(zhí)行,那么bbb是同步還是異步的呢,查看打印結(jié)果

發(fā)現(xiàn)abc分別在三個(gè)線程,所以bbb是異步的;
如果 q.async(execute: work)改成同步呢;
再打印一下看看

由于work是同步的,所以aaa走在了ccc前面,但是bbb仍然是異步的,因此notify方法添加的任務(wù)將會(huì)異步執(zhí)行.
- cancel()
let q = DispatchQueue.global()
item = DispatchWorkItem.init(block: { [weak self] in
for i in 0 ... 100000{
if self?.item?.isCancelled ?? false{
break
}
print(i)
}
})
if item != nil{
q.async(execute: item!)
}
q.asyncAfter(deadline: .now() + 0.1) {
self.item?.cancel()
}
這段代碼只能輸出幾千次,乍一看是中斷了任務(wù),實(shí)際上如果把break那三行注釋掉,還是會(huì)輸出100000次.
從這個(gè)例子能看出來,cancel()壓根不能中斷正在執(zhí)行的任務(wù),因?yàn)楸举|(zhì)上不是取消DispatchWorkItem的任務(wù),只是標(biāo)記為取消,當(dāng)后續(xù)再次嘗試執(zhí)行時(shí),如果標(biāo)記了取消(isCancelled屬性),則不會(huì)再嘗試執(zhí)行,對于已經(jīng)開始的任務(wù),并不會(huì)中斷執(zhí)行,說白了省得自定義一個(gè)flag而已.
二: DispatchGroup
DispatchGroup和OC的Group基本一樣,簡單舉個(gè)例子
func async(group: DispatchGroup, execute workItem: DispatchWorkItem)
func async(group: DispatchGroup? = nil, qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], execute work: @escaping @convention(block) () -> Void)
直接init構(gòu)造,使用上面兩個(gè)方法添加任務(wù),指定優(yōu)先級(jí)等屬性;
可以看到?jīng)]有同步向group添加任務(wù)的方法,只能異步.
let group = DispatchGroup.init()
DispatchQueue.global().async(group: group){
print("aaa")
}
DispatchQueue.global().async(group: group){
print("bbb")
}
DispatchQueue.global().async(group: group){
print("ccc")
}
group.notify(queue: DispatchQueue.main) {
print("finish")
}
DispatchGroup的notify方法添加的任務(wù),會(huì)在組內(nèi)所有任務(wù)完成后執(zhí)行.
let group = DispatchGroup.init()
DispatchQueue.global().async(group: group){
DispatchQueue.global().async {
sleep(2)
print("aaa")
}
}
DispatchQueue.global().async(group: group){
print("bbb")
}
DispatchQueue.global().async(group: group){
print("ccc")
}
group.notify(queue: DispatchQueue.main) {
print("finish")
}

上面這樣的例子,group添加的第一個(gè)任務(wù)會(huì)立即返回,導(dǎo)致finish比aaa更早輸出,這里可以使用enter()好leave()來控制,和OC的使用起來完全一樣.
let group = DispatchGroup.init()
group.enter()
DispatchQueue.global().async(group: group){
DispatchQueue.global().async {
sleep(2)
print("aaa")
group.leave()
}
}
DispatchQueue.global().async(group: group){
print("bbb")
}
DispatchQueue.global().async(group: group){
print("ccc")
}
group.notify(queue: DispatchQueue.main) {
print("finish")
}
只需要給異步的任務(wù)加上enter和leave就可以了

- wait
DispatchGroup也有wait()方法,wait會(huì)阻塞線程,會(huì)等待前面的任務(wù)完成,然后再執(zhí)行后面的,因?yàn)樘砑尤蝿?wù)的代碼寫在當(dāng)前線程,所以它連group自己也能阻塞.
let group = DispatchGroup.init()
DispatchQueue.global().async(group: group) {
sleep(2)
print("a")
}
// let _ = group.wait()
DispatchQueue.global().async(group: group) {
print("b")
}
print("c")
在上面這個(gè)例子里,注釋wait打印順序是cba,打開注釋,打印順序是acb
let group = DispatchGroup.init()
DispatchQueue.global().async(group: group) {
sleep(2)
print("a")
}
DispatchQueue.global().async(group: group) {
print("b")
}
let _ = group.wait()
print("c")
調(diào)整一下位置,打印順序是bac
let group = DispatchGroup.init()
DispatchQueue.global().async(group: group) {
sleep(5)
print("a")
}
let _ = group.wait(timeout: .now() + 3)
DispatchQueue.global().async(group: group) {
print("b")
}
print("c")
wait可以設(shè)置一個(gè)DispatchTime參數(shù),這個(gè)參數(shù)的作用是設(shè)置一個(gè)最小的等待時(shí)間,如果等待時(shí)間過了,前面的任務(wù)還有沒完成的,那也不等了,直接返回;
所以上面這個(gè)例子打印順序是bca;
如果把等待時(shí)間改成6秒,就能等到a執(zhí)行,會(huì)輸出acb;
三.DispatchAfter
func asyncAfter(deadline: DispatchTime, execute: DispatchWorkItem)
func asyncAfter(deadline: DispatchTime, qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], execute work: @escaping () -> Void)
DispatchAfter是隊(duì)列的方法,有兩個(gè),只能添加異步任務(wù),可以指定優(yōu)先級(jí);
對于deadline,是需要等待的時(shí)間,文檔說明了兩點(diǎn),一是不能設(shè)置為現(xiàn)在(.now),這樣做比直接執(zhí)行效率要低;二是不能設(shè)置為永久(.distantFuture),這么做沒意義
let q = DispatchQueue.init(label: "queue")
q.async {
q.asyncAfter(deadline: .now() + 0.001) {
print("b -- \(Thread.current)")
}
for i in 0 ..< 100000{
print("a = \(i) -- \(Thread.current)")
}
}

上面這段代碼中a會(huì)打印很久,b要一直等到a輸出完,才能輸出,因此DispatchAfter并不會(huì)開啟新的線程,a和b都在線程4中執(zhí)行.
四.其他補(bǔ)充
1.重復(fù)執(zhí)行
class func concurrentPerform(iterations: Int, execute work: (Int) -> Void)
DispatchQueue.concurrentPerform(iterations: 10) { (i) in
print("i -- \(Thread.current)")
}

這個(gè)方法可以高效的創(chuàng)建并發(fā)for循環(huán),這是個(gè)靜態(tài)方法所以不能指定隊(duì)列,一定會(huì)創(chuàng)建很多個(gè)線程,所以尤其需要注意線程安全.
2.關(guān)于Target
convenience init(label: String, qos: DispatchQoS = .unspecified, attributes: DispatchQueue.Attributes = [], autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit, target: DispatchQueue? = nil)
在自定義隊(duì)列的時(shí)候,有一個(gè)屬性是target,那么它是什么作用呢,文檔里寫了一大堆閱讀理解,總結(jié)一下就是:
1.可以把隊(duì)列Q的任務(wù)分配到target隊(duì)列中,使用target的優(yōu)先級(jí)策略,但是仍然保持Q的語義,比如串行或者并發(fā)
2.可以把多個(gè)隊(duì)列的目標(biāo)隊(duì)列設(shè)置為同一個(gè),這些任務(wù)會(huì)按照添加進(jìn)target的順序執(zhí)行
3.不能相設(shè)置兩個(gè)隊(duì)列互為target
一般會(huì)用作給隊(duì)列分組,將不同的任務(wù)放到多個(gè)隊(duì)列管理,然后指向固定的隊(duì)列或者系統(tǒng)隊(duì)列再執(zhí)行任務(wù).
3.唯一執(zhí)行
swift移除了Dispatch_once,但是在swift中有很多方法來實(shí)現(xiàn)唯一單次執(zhí)行,比如全局的變量,類與結(jié)構(gòu)體的靜態(tài)屬性等.