本文不涉及 GCD 的概念和理論,僅記錄了 GCD 在一些開發場景下的應用。 嗯,歡迎你們積極留言補充。swift
這是應用最普遍的場景,爲了不阻塞主線程,將耗時操做放在子線程處理,而後在主線程使用處理結果。好比讀取沙盒中的一些數據,而後將讀取的數據展現在 UI,這個場景還有幾個細分:api
/// 主線程須要子線程的處理結果
func handle<T>(somethingLong: @escaping () -> T, finshed: @escaping (T) -> ()) {
globalQueue.async {
let data = somethingLong()
self.mainQueue.async {
finshed(data)
}
}
}
/// 主線程不須要子線程的處理結果
func handle(somethingLong: @escaping () -> (), finshed: @escaping () -> ()) {
let workItem = DispatchWorkItem {
somethingLong()
}
globalQueue.async(execute: workItem)
workItem.wait()
finshed()
}
/////////////////////////////////////////////////////////////////////////////
GCDKit().handle(somethingLong: { [weak self] in
self?.color = UIColor.red
sleep(2)
}) { [weak self] in
self?.view.backgroundColor = self?.color
}
GCDKit().handle(somethingLong: {
let p = Person()
p.age = 40
print(Date(), p.age)
sleep(2)
return p
}) { (p: Person) in
print(Date(), p.age)
}
複製代碼
每一段子任務依賴上一個任務完成,所有完成後回調主線程:安全
/// 向全局併發隊列添加任務,添加的任務會同步執行
func wait(code: @escaping GCDKitHandleBlock) -> GCDKit {
handleBlockArr.append(code)
return self
}
/// 處理完畢的回調,在主線程異步執行
func finshed(code: @escaping GCDKitHandleBlock) {
globalQueue.async {
for workItem in self.handleBlockArr {
workItem()
}
self.handleBlockArr.removeAll()
self.mainQueue.async {
code()
}
}
}
/////////////////////////////////////////////////////////////////////////////
GCDKit().wait {
self.num += 1
}.wait {
self.num += 2
}.wait {
self.num += 3
}.wait {
self.num += 4
}.wait {
self.num += 5
}.finshed {
print(self.num, Thread.current)
}
複製代碼
每一段子任務獨立,全部子任務完成後回調主線程:網絡
/// 向自定義併發隊列添加任務,添加的任務會併發執行
func handle(code: @escaping GCDKitHandleBlock) -> GCDKit {
let queue = DispatchQueue(label: "", attributes: .concurrent)
let workItem = DispatchWorkItem {
code()
}
queue.async(group: group, execute: workItem)
return self
}
/// 此任務執行時會排斥其餘的併發任務,通常用於寫入事務,保證線程安全。
func barrierHandle(code: @escaping GCDKitHandleBlock) -> GCDKit {
let queue = DispatchQueue(label: "", attributes: .concurrent)
let workItem = DispatchWorkItem(flags: .barrier) {
code()
}
queue.async(group: group, execute: workItem)
return self
}
/// 處理完畢的回調,在主線程異步執行
func allDone(code: @escaping GCDKitHandleBlock) {
group.notify(queue: .main, execute: {
code()
})
}
/////////////////////////////////////////////////////////////////////////////
GCDKit().barrierHandle {
self.num += 1
}.barrierHandle {
self.num += 2
}.barrierHandle {
self.num += 3
}.handle {
self.num += 4
}.handle {
self.num += 5
}.allDone {
self.num += 6
print(self.num, Thread.current)
}
複製代碼
延時一段時間後執行代碼,通常見於打開 App 一段時間後,彈出求好評對話框。多線程
func run(when: DispatchTime, code: @escaping GCDKitHandleBlock) {
DispatchQueue.main.asyncAfter(deadline: when) {
code()
}
}
/////////////////////////////////////////////////////////////////////////////
GCDKit().run(when: .now() + .seconds(120)) {
self.doSomething()
}
複製代碼
因爲 Timer 的 Target 是強引用,對於 Timer 的銷燬須要特別處理,此外,Timer 的運行依賴於 Runloop,在 Runloop 的一次循環中,Timer 也只會執行一次,這使得在 Runloop 負擔比較重時,可能會跳過 Timer 的執行,所以,在用到定時器的地方,你也能夠用 CGD 的 TimerSource 替代:併發
/// 計時器
///
/// - Parameters:
/// - start: 開始時間
/// - end: 結束時間
/// - repeating: 多久重複一次
/// - leeway: 容許偏差
/// - eventHandle: 處理事件
/// - cancelHandle: 計時器結束事件
func timer(start: DispatchTime, end: DispatchTime, repeating: Double, leeway: DispatchTimeInterval, eventHandle: @escaping GCDKitHandleBlock, cancelHandle: GCDKitHandleBlock? = nil)
{
let timer = DispatchSource.makeTimerSource()
timer.setEventHandler {
eventHandle()
}
timer.setCancelHandler {
cancelHandle?()
}
timer.schedule(deadline: start, repeating: repeating, leeway: leeway)
timer.resume()
run(when: end) {
timer.cancel()
}
}
/////////////////////////////////////////////////////////////////////////////
GCDKit().timer(start: .now(),
end: .now() + .seconds(10),
repeating: 2,
leeway: .milliseconds(1),
eventHandle: {
self.doSomething()
}) {
print("timer cancel")
}
複製代碼
若是你須要更快的處理數據,能夠用 concurrentPerform
讓循環操做併發執行:app
func map<T>(data: [T], code: (T) -> ()) {
DispatchQueue.concurrentPerform(iterations: data.count) { (i) in
code(data[i])
}
}
func run(code: (Int) -> (), repeting: Int) {
DispatchQueue.concurrentPerform(iterations: repeting) { (i) in
code(i)
}
}
/////////////////////////////////////////////////////////////////////////////
let data = [1, 2, 3]
var sum = 0
GCDKit().map(data: data) { (ele: Int) in
sleep(1)
sum += ele
}
print(sum)
GCDKit().run(code: { (i) in
sleep(1)
sum += data[i]
}, repeting: data.count)
print(sum)
複製代碼
有時咱們須要併發處理一些任務,可是並不想同時開不少線程,GCD 並無相似 NSOperation 最大併發數的概念,但能夠藉助信號量實現:異步
func doSomething(label: String, cost: UInt32, complete:@escaping ()->()){
NSLog("Start task%@",label)
sleep(cost)
NSLog("End task%@",label)
complete()
}
/////////////////////////////////////////////////////////////////////////////
let semaphore = DispatchSemaphore(value: 3)
let queue = DispatchQueue(label: "", qos: .default, attributes: .concurrent)
queue.async {
semaphore.wait()
self.doSomething(label: "1", cost: 2, complete: {
print(Thread.current)
semaphore.signal()
})
}
queue.async {
semaphore.wait()
self.doSomething(label: "2", cost: 2, complete: {
print(Thread.current)
semaphore.signal()
})
}
queue.async {
semaphore.wait()
self.doSomething(label: "3", cost: 4, complete: {
print(Thread.current)
semaphore.signal()
})
}
queue.async {
semaphore.wait()
self.doSomething(label: "4", cost: 2, complete: {
print(Thread.current)
semaphore.signal()
})
}
queue.async {
semaphore.wait()
self.doSomething(label: "5", cost: 3, complete: {
print(Thread.current)
semaphore.signal()
})
}
複製代碼
時序管理主要有幾種組合狀況:async
參照耗時操做小節。ide
通常見於網絡請求,一個接口的請求參數是另外一個接口的返回值,這種狀況就須要對網絡請求進行時序管理,如下代碼表示一個網絡請求的封裝:
func networkTask(label:String, cost:UInt32, complete:@escaping ()->()){
NSLog("Start network Task task%@",label)
DispatchQueue.global().async {
sleep(cost)
NSLog("End networkTask task%@",label)
DispatchQueue.main.async {
complete()
}
}
}
複製代碼
在子線程可開線程的狀況下,依次執行須要藉助信號量控制:
let semaphore = DispatchSemaphore(value: 1)
let queue = DispatchQueue(label: "", qos: .default, attributes: .concurrent)
queue.async {
semaphore.wait()
self.networkTask(label: "1", cost: 2, complete: {
semaphore.signal()
})
semaphore.wait()
self.networkTask(label: "2", cost: 4, complete: {
semaphore.signal()
})
semaphore.wait()
self.networkTask(label: "3", cost: 3, complete: {
semaphore.signal()
})
semaphore.wait()
self.networkTask(label: "4", cost: 1, complete: {
semaphore.signal()
})
semaphore.wait()
print("all done")
semaphore.signal()
}
/////////////////////////////////////////////////////////////////////////////
2017-12-19 14:02:33.297613+0800 Demo[11757:4946542] Start network Task task1
2017-12-19 14:02:35.301386+0800 Demo[11757:4946541] End networkTask task1
2017-12-19 14:02:35.301971+0800 Demo[11757:4946542] Start network Task task2
2017-12-19 14:02:39.306592+0800 Demo[11757:4946541] End networkTask task2
2017-12-19 14:02:39.306901+0800 Demo[11757:4946542] Start network Task task3
2017-12-19 14:02:42.307843+0800 Demo[11757:4946541] End networkTask task3
2017-12-19 14:02:42.308268+0800 Demo[11757:4946542] Start network Task task4
2017-12-19 14:02:43.310724+0800 Demo[11757:4946541] End networkTask task4
all done
複製代碼
這種狀況多見於須要請求多個接口,所有請求完畢後再進行某些操做,這能夠藉助 GCD 的任務組處理:
let group = DispatchGroup()
group.enter()
networkTask(label: "1", cost: 2, complete: {
group.leave()
})
group.enter()
networkTask(label: "2", cost: 4, complete: {
group.leave()
})
group.enter()
networkTask(label: "3", cost: 2, complete: {
group.leave()
})
group.enter()
networkTask(label: "4", cost: 4, complete: {
group.leave()
})
group.notify(queue: .main, execute:{
print("All network is done")
})
/////////////////////////////////////////////////////////////////////////////
2017-12-19 14:10:33.876393+0800 Demo[16495:4973791] Start network Task task1
2017-12-19 14:10:33.878869+0800 Demo[16495:4973791] Start network Task task2
2017-12-19 14:10:33.879142+0800 Demo[16495:4973791] Start network Task task3
2017-12-19 14:10:33.879309+0800 Demo[16495:4973791] Start network Task task4
2017-12-19 14:10:35.883851+0800 Demo[16495:4974025] End networkTask task1
2017-12-19 14:10:35.883850+0800 Demo[16495:4974030] End networkTask task3
2017-12-19 14:10:37.883995+0800 Demo[16495:4974026] End networkTask task2
2017-12-19 14:10:37.883995+0800 Demo[16495:4974027] End networkTask task4
All network is done
// 你也能夠這樣進行簡寫
let downloadGroup = DispatchGroup()
GCDKit().run(code: { (i) in
downloadGroup.enter()
networkTask(label: "\(i)", cost: UInt32(i), complete: {
downloadGroup.leave()
})
}, repeting: 10)
downloadGroup.notify(queue: .main) {
print("All network is done")
}
/////////////////////////////////////////////////////////////////////////////
2017-12-19 15:07:13.253428+0800 Demo[49319:5169745] Start network Task task3
2017-12-19 15:07:13.253428+0800 Demo[49319:5169743] Start network Task task2
2017-12-19 15:07:13.253428+0800 Demo[49319:5169744] Start network Task task0
2017-12-19 15:07:13.253479+0800 Demo[49319:5169474] Start network Task task1
2017-12-19 15:07:13.253946+0800 Demo[49319:5169744] Start network Task task6
2017-12-19 15:07:13.253947+0800 Demo[49319:5169743] Start network Task task4
2017-12-19 15:07:13.253947+0800 Demo[49319:5169745] Start network Task task5
2017-12-19 15:07:13.254119+0800 Demo[49319:5169763] End networkTask task0
2017-12-19 15:07:13.254193+0800 Demo[49319:5169474] Start network Task task7
2017-12-19 15:07:13.254339+0800 Demo[49319:5169744] Start network Task task8
2017-12-19 15:07:13.254343+0800 Demo[49319:5169743] Start network Task task9
2017-12-19 15:07:14.258061+0800 Demo[49319:5169764] End networkTask task1
2017-12-19 15:07:15.258071+0800 Demo[49319:5169762] End networkTask task2
2017-12-19 15:07:16.258189+0800 Demo[49319:5169742] End networkTask task3
2017-12-19 15:07:17.258100+0800 Demo[49319:5169745] End networkTask task4
2017-12-19 15:07:18.258196+0800 Demo[49319:5169766] End networkTask task5
2017-12-19 15:07:19.258171+0800 Demo[49319:5169765] End networkTask task6
2017-12-19 15:07:20.259119+0800 Demo[49319:5169763] End networkTask task7
2017-12-19 15:07:21.258239+0800 Demo[49319:5169767] End networkTask task8
2017-12-19 15:07:22.258280+0800 Demo[49319:5169744] End networkTask task9
All network is done
複製代碼
當須要監聽某個數據的變化,但不須要頻繁的調用其對應的回調處理,可使用 DispatchSourceUserData
進行監聽,它會自動合併更改,並在隊列空閒時進行回調,以節省 CPU 開銷。
extension GCDKit {
convenience init(valueChanged: @escaping (T) -> ()) {
self.init()
userDataAddSource = DispatchSource.makeUserDataAddSource()
userDataAddSource?.setEventHandler(handler: { [weak self] in
guard let `self` = self else { return }
guard let value = self.value else { return }
valueChanged(value)
})
userDataAddSource?.resume()
}
func send(_ value: T) {
self.value = value
userDataAddSource?.add(data: 1)
}
}
/////////////////////////////////////////////////////////////////////////////
GCD = GCDKit<Int> { (value: Int) in
print(value)
}
let serialQueue = DispatchQueue(label: "com")
serialQueue.async {
for i in 1...1000{
self.GCD.send(i)
}
for i in 1000...9999 {
self.GCD.send(i)
}
}
/////////////////////////////////////////////////////////////////////////////
64
9999
複製代碼
在 Mac 開發中,你能夠監聽其餘進程的開啓關閉狀況:
let apps = NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.mail")
let processIdentifier = apps.first?.processIdentifier
let source = DispatchSource.makeProcessSource(identifier: pid, eventMask: .exit)
source.setEventHandler {
print("mail quit")
}
source.resume()
複製代碼
let folder = try? FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
print(folder!.path)
let fd = open(folder!.path, O_CREAT, 0o644)
let queue = DispatchQueue(label: "m")
let source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fd,
eventMask: .all,
queue: queue)
source.setEventHandler {
print("folder changed")
}
source.resume()
let result = FileManager.default.createFile(atPath: folder!.path + "/abc", contents: nil, attributes: nil)
if result {
print(0)
}
else {
print(1)
}
複製代碼
你能夠在資源讀寫時對其所在線程進行一些限制,而沒必要使用線程鎖,好比:
/// .barrier 保證執行時會排斥其餘的併發任務,通常用於寫入事務,保證線程安全。
func barrierHandle(code: @escaping GCDKitHandleBlock) -> GCDKit {
let queue = DispatchQueue(label: "", attributes: .concurrent)
let workItem = DispatchWorkItem(flags: .barrier) {
code()
}
queue.async(group: group, execute: workItem)
return self
}
複製代碼
或者開啓一個串行隊列同步讀寫任務:
extension GCDKit {
var data: T? {
get {
return readWriteQueue.sync { value }
}
set {
readWriteQueue.sync { value = newValue }
}
}
}
複製代碼