一、DispatchQueue
DispatchQueue 分為串行和并發(fā),它的完整初始化方法為:
init(label: String, qos: DispatchQoS = default, attributes: DispatchQueue.Attributes = default, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = default, target: DispatchQueue? = default)
可見吝羞,這些參數(shù)中兰伤,除了label,其它都有默認值(label表示該隊列的標簽钧排,建議傳值為反向域名字符串敦腔,如:com.onevcat.Kingfisher.Animator.preloadQueue)。
當(dāng)除label外的參數(shù)都使用默認值時卖氨,初始化方法返回的便是串行隊列会烙。如果需要返回并發(fā)隊列,參數(shù)attributes傳值為.concurrent
即可筒捺。DispatchQueue.Attributes 是一個結(jié)構(gòu)體類型柏腻,該結(jié)構(gòu)體提供了兩個靜態(tài)變量:concurrent
和initiallyInactive
(注意,沒有代表串行隊列的靜態(tài)變量)系吭。如果attributes參數(shù)傳值為initiallyInactive
, 任務(wù)不會自動執(zhí)行五嫂,而是需要開發(fā)者手動調(diào)用activate()
觸發(fā)。但是代碼依然是串行進行的,如果想要手動觸發(fā)肯尺、并行執(zhí)行任務(wù),可以指定attributes參數(shù)接受一個數(shù)組: [.concurrent, .initiallyInactive]
沃缘。
參數(shù)qos代表隊列執(zhí)行的優(yōu)先級,有六種優(yōu)先級可供選擇:
unspecified
background
default
utility
userInteractive
userInitiated
優(yōu)先級從高到低依次為userInteractive>userInitiated>utility>background, 而default與unspecified介于userInteractive與background之間则吟,具體有系統(tǒng)決定槐臀。
DispatchQueue.AutoreleaseFrequency有三種屬性值.inherit
、.workItem
和.never
氓仲。
.inherit
:不確定水慨,之前默認的行為也是現(xiàn)在的默認值
.workItem
:為每個執(zhí)行的任務(wù)創(chuàng)建自動釋放池,項目完成時清理臨時對象
.never
:GCD不為您管理自動釋放池
參數(shù)target 用于指定即將創(chuàng)建的隊列與隊列target優(yōu)先級相同。也可通過setTarget(queue: DispatchQueue?)
函數(shù)指定與queue相同的優(yōu)先級敬扛。
除了開發(fā)者自己創(chuàng)建隊列晰洒,還可以通過DispatchQueue.main
獲取主隊列(主隊列也屬于串行隊列)、DispatchQueue.global(qos: DispatchQoS.QoSClass)
獲取全局并發(fā)隊列啥箭。
創(chuàng)建好了隊列谍珊,通過sync { /*任務(wù)*/ }
或 async { /*任務(wù)*/ }
將任務(wù)追加到隊列中。串行隊列或并發(fā)隊列與sync或async組合總結(jié):
串行隊列 + sync : 隊列中的任務(wù)在當(dāng)前線程中依次執(zhí)行急侥,后面追加的任務(wù)會等到前面追加的任務(wù)執(zhí)行完了才開始執(zhí)行砌滞,不開新線程。當(dāng)前線程取任務(wù)執(zhí)行的隊列不能與該串行隊列相同坏怪,否則會發(fā)生線程死鎖贝润。
串行隊列(非主隊列) + async
: 隊列中的任務(wù)在新線程中依次執(zhí)行。
主隊列 + async
: 將任務(wù)追加到主隊列陕悬,當(dāng)主隊列中的其他任務(wù)執(zhí)行完之后才會執(zhí)行题暖,并且在在主線程中執(zhí)行按傅。
并發(fā)隊列 + sync
: 隊列中的任務(wù)在當(dāng)前線程中依次執(zhí)行捉超。
并發(fā)隊列 + async
: 隊列中的任務(wù)在新線程中并發(fā)執(zhí)行胧卤。
不管哪種組合,隊列中的任務(wù)出列的方式都是FIFO拼岳。
有時候希望追加到queue中的任務(wù)暫不執(zhí)行枝誊,等待某一時刻執(zhí)行,這時候可使用隊列的suspend()
函數(shù)和resume()
函數(shù)惜纸。suspend()
函數(shù)使隊列的暫停計數(shù)加1叶撒,resume()
函數(shù)使隊列的暫停計數(shù)減一。
需要注意:
1耐版、suspend()和resume()需要成對出現(xiàn)祠够,否則會crash。
2粪牲、suspend()和resume()函數(shù)只對自己創(chuàng)建的隊列有效古瓤,對系統(tǒng)提供的全局隊列無效。
3腺阳、suspend()和 resume()對隊列中的還未執(zhí)行的任務(wù)有效落君,對于正在執(zhí)行的任務(wù)無效。
二亭引、DispatchGroup
在追加到DispatchQueue中的多個處理全部結(jié)束后想執(zhí)行結(jié)束處理绎速,這個時候就可用到DispatchGroup。示例如下:
let group = DispatchGroup()
let queue = DispatchQueue.global()
queue.async(group: group) {
print("任務(wù)一")
}
queue.async(group: group) {
print("任務(wù)二")
}
queue.async(group: group) {
print("任務(wù)三")
}
group.notify(queue: DispatchQueue.main) {
print("完成任務(wù)一焙蚓、二纹冤、三")
}
queue.async {
print("任務(wù)四")
}
運行結(jié)果:
其中,queue既可以是同一個隊列主届,也可以是不同的隊列赵哲,既可以是串行隊列,也可以是并發(fā)隊列君丁。
另外枫夺,也可以使用group的
group.wait(timeout: DispatchTime)
或group.wait(wallTimeout: DispatchWallTime)
函數(shù)。wait 函數(shù)的參數(shù)表示等待的時間绘闷,默認是 DispatchTime.distantFuture
橡庞,表示永久等待。wait函數(shù)會阻塞當(dāng)前線程印蔗,即當(dāng)執(zhí)行的時間到了等待的時長扒最,才會執(zhí)行后面的代碼。wait函數(shù)返回值是枚舉類型DispatchTimeoutResult华嘹,DispatchTimeoutResult有success
吧趣、timeOut
兩個枚舉值,分別表示在等待時長內(nèi),任務(wù)執(zhí)行完成和未完成强挫。我們還可以通過group的
enter()
函數(shù)和leave()
函數(shù)顯式表明任務(wù)是否執(zhí)行完成岔霸。代碼如下:
let group = DispatchGroup()
let queue = DispatchQueue.global()
group.enter()
queue.async {
print("任務(wù)一")
group.leave()
}
group.enter()
queue.async {
print("任務(wù)二")
group.leave()
}
group.enter()
queue.async {
print("任務(wù)三")
group.leave()
}
group.notify(queue: DispatchQueue.main) {
print("完成任務(wù)一、二俯渤、三")
}
queue.async {
print("任務(wù)四")
}
運行結(jié)果:
enter()
和leave()
必須配合使用呆细,有幾次enter就要有幾次leave,否則group會一直存在八匠。當(dāng)所有enter的block都leave后絮爷,會執(zhí)行dispatch_group_notify的block。
三梨树、asyncAfter
該函數(shù)用于延時操作坑夯。代碼如下:
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now()+3) {
print("執(zhí)行任務(wù)")
}
或
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+3) {
print("執(zhí)行任務(wù)")
}
注意, asyncAfter
函數(shù)并不是在指定時間后執(zhí)行處理,而是在指定時間后將任務(wù)追加到隊列中抡四。
asyncAfter函數(shù)的第一個參數(shù)可以是DispatchTime類型的值渊涝,也可以是DispatchWallTime類型的值。
DispatchTime 表示相對時間(相對設(shè)備啟動的時間床嫌,當(dāng)設(shè)備休眠時跨释,計時也會暫停),精度為納秒級厌处。DispatchTime.now()
獲取當(dāng)前相對時間鳖谈,DispatchTime.now()
基于當(dāng)前時間三秒后的時間,表達式中的3也可以使用DispatchTimeInterval.seconds(3)
替換阔涉,或者用其他的時間單位:毫秒級DispatchTimeInterval.milliseconds(Int)
缆娃、微秒級DispatchTimeInterval.milliseconds(Int)
、納秒級DispatchTimeInterval.nanoseconds(Int)
瑰排。
DispatchWallTime 表示絕對時間(系統(tǒng)時間贯要,設(shè)備休眠計時不暫停),精度是微秒椭住。DispatchWallTime的用法和DispatchTime差不多崇渗。
四、DispatchWorkItem
DispatchWorkItem可以將任務(wù)封裝成DispatchWorkItem對象京郑。
let workItem = DispatchWorkItem.init {
print("執(zhí)行任務(wù)")
}
可以調(diào)用workItem的perform()
函數(shù)執(zhí)行任務(wù)宅广,也可以將workItem追加到DispatchQueue或DispatchGroup中。以上所有傳block的地方都可換成DispatchWorkItem對象些举。
DispatchQueue還可以使用notify
函數(shù)觀察workItem中的任務(wù)執(zhí)行結(jié)束跟狱,以及通過cancel()
函數(shù)取消任務(wù)。
另外户魏,workItem也可以像DispatchGroup一樣調(diào)用wait()
函數(shù)等待任務(wù)完成驶臊。需要注意的是挪挤,追加workItem的隊列或調(diào)用perform()
所在的隊列不能與調(diào)用workItem.wait()
的隊列是同一個隊列,否則會出現(xiàn)線程死鎖关翎。
DispatchWorkItem的完整初始化方法:
init(qos: DispatchQoS, flags: DispatchWorkItemFlags, block: () -> Void)
DispatchQoS前面已經(jīng)說過电禀,不再贅述。DispatchWorkItemFlags類型的變量有六種:
static let assignCurrentContext: DispatchWorkItemFlags
static let barrier: DispatchWorkItemFlags
static let detached: DispatchWorkItemFlags
static let enforceQoS: DispatchWorkItemFlags
static let inheritQoS: DispatchWorkItemFlags
static let noQoS: DispatchWorkItemFlags
為了高效地讀寫數(shù)據(jù)庫或文件笤休,通常需要將讀寫處理追加到并發(fā)隊列
中異步執(zhí)行,為了使讀寫操作不會引發(fā)數(shù)據(jù)競爭的問題症副,寫入操作不能與其他的寫入操作以及包含讀取任務(wù)的操作并發(fā)處理店雅,這時便可設(shè)置flag的值為.barrier
。代碼如下:
let queue = DispatchQueue.init(label: "com.codansYC.queue", attributes: DispatchQueue.Attributes.concurrent)
queue.async {
print("讀數(shù)據(jù)1")
}
queue.async {
print("讀數(shù)據(jù)2")
}
let workItem = DispatchWorkItem.init(qos: DispatchQoS.default, flags: DispatchWorkItemFlags.barrier) {
print("開始寫數(shù)據(jù)------寫數(shù)據(jù)完成")
}
queue.async(execute: workItem)
queue.async {
print("讀數(shù)據(jù)3")
}
queue.async {
print("讀數(shù)據(jù)4")
}
運行結(jié)果:
注意贞铣,
barrier
只對自己創(chuàng)建的并發(fā)隊列才有效闹啦,對系統(tǒng)提供的全局并發(fā)隊列無效。
五辕坝、DispatchQueue.concurrentPerform
sync函數(shù)和Dispatch Group的關(guān)聯(lián)API窍奋。
DispatchQueue.concurrentPerform
會按指定次數(shù)異步執(zhí)行任務(wù),并且會等待指定次數(shù)的任務(wù)全部執(zhí)行完成酱畅,即會阻塞線程琳袄。建議在子線程中使用。
DispatchQueue.global().async {
DispatchQueue.concurrentPerform(iterations: 5) { (i) in
print("執(zhí)行任務(wù)\(i+1)")
}
print("任務(wù)執(zhí)行完成")
}
運行結(jié)果:六纺酸、DispatchSemaphore
信號量窖逗。用于控制訪問資源的數(shù)量。比如系統(tǒng)有兩個資源可以被利用餐蔬,同時有三個線程要訪問碎紊,只能允許兩個線程訪問,第三個會等待資源被釋放后再訪問樊诺。
信號量的初始化方法:DispatchSemaphore.init(value: Int)
仗考,value表示允許訪問資源的線程數(shù)量,當(dāng)value為0時對訪問資源的線程沒有限制词爬。
信號量配套使用wait()
函數(shù)與signal()
函數(shù)控制訪問資源秃嗜。
wait
函數(shù)會阻塞當(dāng)前線程直到信號量計數(shù)大于或等于1,當(dāng)信號量大于或等于1時顿膨,將信號量計數(shù)-1, 然后執(zhí)行后面的代碼痪寻。signal()
函數(shù)會將信號量計數(shù)+1。
信號量是GCD同步的一種方式虽惭。前面介紹過的DispatchWorkItemFlags.barrier
是對queue中的任務(wù)進行批量同步處理橡类,sync函數(shù)是對queue中的任務(wù)單個同步處理,而DispatchSemaphore是對queue中的某個任務(wù)中的某部分(某段代碼)同步處理芽唇。此時將DispatchSemaphore.init(value: Int)
中的參數(shù)value傳入1顾画。代碼如下:
var arr = [Int]()
let semaphore = DispatchSemaphore.init(value: 1) // 創(chuàng)建信號量取劫,控制同時訪問資源的線程數(shù)為1
for i in 0...100 {
DispatchQueue.global().async {
/*
其他并發(fā)操作
*/
semaphore.wait() // 如果信號量計數(shù)>=1,將信號量計數(shù)減1;如果信號量計數(shù)<1研侣,阻塞線程直到信號量計數(shù)>=1
arr.append(i)
semaphore.signal() // 信號量計加1
/*
其他并發(fā)操作
*/
}
}