13 個 GCD 應用場景

iOS開發2018-12-10 06:52:24

程序君個人微信
和我聊聊編程和創業的事
加好友


作者丨薛定諤

https://juejin.im/post/5a38c19c6fb9a0450809c77c


本文不涉及 GCD 的概念和理論,僅記錄了 GCD 在一些開發場景下的應用。


嗯,歡迎大家積極留言補充。


耗時操作


這是應用最廣泛的場景,為了避免阻塞主線程,將耗時操作放在子線程處理,然後在主線程使用處理結果。比如讀取沙盒中的一些數據,然後將讀取的數據展示在 UI,這個場景還有幾個細分:


執行一個耗時操作後回調主線程


/// 主線程需要子線程的處理結果
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 selfin
    self?.color = UIColor.red
    sleep(2)
}) { [weak selfin
    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 讓循環操作併發執行:


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 = [123]
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: 2complete: {
        print(Thread.current)
        semaphore.signal()
    })
}

queue.async {
    semaphore.wait()
    self.doSomething(label: "2"cost: 2complete: {
        print(Thread.current)
        semaphore.signal()
    })
}

queue.async {
    semaphore.wait()
    self.doSomething(label: "3"cost: 4complete: {
        print(Thread.current)
        semaphore.signal()
    })
}

queue.async {
    semaphore.wait()
    self.doSomething(label: "4"cost: 2complete: {
        print(Thread.current)
        semaphore.signal()
    })
}

queue.async {
    semaphore.wait()
    self.doSomething(label: "5"cost: 3complete: {
        print(Thread.current)
        semaphore.signal()
    })
}


時序管理


時序管理主要有幾種組合情況:


  • 子任務內是否開線程;

  • 子任務是否依次執行;


子任務內不開線程依次執行


參照耗時操作小節。


子任務內開線程依次執行



一般見於網絡請求,一個接口的請求參數是另一個接口的返回值,這種情況就需要對網絡請求進行時序管理,以下代碼表示一個網絡請求的封裝:


func networkTask(label:Stringcost:UInt32complete:@escaping ()->()){
    NSLog("Start network Task task%@",label)
    DispatchQueue.global().async {
        sleep(cost)
        NSLog("End networkTask task%@",label)
        DispatchQueue.main.async {
            complete()
        }
    }
}


在子線程可開線程的情況下,依次執行需要藉助信號量控制:


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


子任務內開線程不依次執行


這種情況多見於需要請求多個接口,全部請求完畢後再進行某些操作,這可以藉助 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 selfin
            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,
                                          createfalse)
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(groupgroup, execute: workItem)
    return self
}


或者開啟一個串行隊列同步讀寫任務:


extension GCDKit {

    var data: T? {
        get {
            return readWriteQueue.sync { value }
        }
        set {
            readWriteQueue.sync { value = newValue }
        }
    }
}


 推薦↓↓↓ 

👉16個技術公眾號】都在這裡!

涵蓋:程序員大咖、源碼共讀、程序員共讀、數據結構與算法、黑客技術和網絡安全、大數據科技、編程前端、Java、Python、Web編程開發、Android、iOS開發、Linux、數據庫研發、幽默程序員等。

https://weiwenku.net/d/109744585