swift学习之多线程(四)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 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 让循环操作并发执行:
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() }) }
六,时序管理
时序管理主要有几种组合情况:“子任务内是否开线程,子任务是否依次执行”;
-
子任务内不开线程依次执行
参照耗时操作小节。
-
子任务内开线程依次执行
一般见于网络请求,一个接口的请求参数是另一个接口的返回值,这种情况就需要对网络请求进行时序管理,以下代码表示一个网络请求的封装:
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 } } } }
浙公网安备 33010602011771号