I have DispatchWorkItem that fetch some info and I have a button that displays the info. The worker looks like this:
worker = DispatchWorkItem {
let info = getInfo()
DispatchQueue.main.async {
self.setInfo(info)
}
}
on viewDidLoad I add the worker to the global queue
DispatchQueue.global().asyncAfter(deadline: .now() + 0.35, execute: worker!)
When the user clicks on the button I need the execution to be blocked until the worker has finished execution.
#IBAction func readInfo(_ sender: UIButton) {
// WAIT UNTIL THE WORKER HAS FINISHED EXECUTION
...
I managed to do so by having the worker looking like this:
worker = DispatchWorkItem {
let info = getInfo()
self.setInfo(info)
}
and by checking if the info was set every 200 ms after the user clicks on the button:
#IBAction func readInfo(_ sender: UIButton) {
while(info == nil){
usleep(2000)
}
...
However I want all the variables to be accessible only by the main thread.
To be honest, I don't clearly understand what you wanted, but it's inappropriate to use such a thing as usleep(2000).
So, there is a possible solution, but it's general and you probably need to modify it for your needs.
let group = DispatchGroup()
var info: Info?
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global().asyncAfter(deadline: .now() + 0.35) { [weak self] in
self?.getInfo()
}
}
func getInfo() {
group.enter()
asyncFunc()
group.notify(queue: .main) { [weak self] in
self?.setInfo(self?.info)
}
}
func asyncFunc() {
..... { [weak self] info in
self?.info = info
self?.group.leave()
}
}
If you want to disable user interaction while something is loading, it's better to show progressive loader, but not just freeze the application. In case with the loader, users will understand, that the app isn't frozen.
There is an example of using DispatchWorkItem with DispatchGroup:
let dispatchWorkItem = DispatchWorkItem{
print("work item start")
sleep(1)
print("work item end")
}
let dg = DispatchGroup()
//submiy work items to the group
let dispatchQueue = DispatchQueue(label: "custom dq")
dispatchQueue.async(group: dg) {
print("block start")
sleep(2)
print("block end")
}
DispatchQueue.global().async(group: dg, execute: dispatchWorkItem)
//print message when all blocks in the group finish
dg.notify(queue: DispatchQueue.global()) {
print("dispatch group over")
}
Code from here
Related
I have a (custom, linked-list based) queue that I want to deserialize when the app starts and serialize when the app stops, like so (AppDelegate.swift):
func applicationWillResignActive(_ application: UIApplication) {
RequestManager.shared.serializeAndPersistQueue()
}
func applicationDidBecomeActive(_ application: UIApplication) {
RequestManager.shared.deserializeStoredQueue()
}
The issue is during serialization when I exit the app. Here's the code that's running:
public func serializeAndPersistQueue() {
do {
let encoder = JSONEncoder()
let data = try encoder.encode(queue) // Bad access here
if FileManager.default.fileExists(atPath: url.path) {
try FileManager.default.removeItem(at: url)
}
FileManager.default.createFile(atPath: url.path, contents: data, attributes: nil)
}
catch {
print(error)
}
}
As you can see, fairly straightforward. It uses the JSONEncoder to convert my queue to a data object, then writes that data to the file at url.
However, during encoder.encode() I get EXC_BAD_ACCESS every time, without fail.
Additionally, I should note that peak and dequeue operations are conducted on the queue from a background thread. I'm not sure if that makes a difference due to my lack of understanding surrounding GCD. Here's what that method looks like:
private func processRequests() {
DispatchQueue.global(qos: .background).async { [unowned self] in
let group = DispatchGroup()
let semaphore = DispatchSemaphore(value: 0)
while !self.queue.isEmpty {
group.enter()
let request = self.queue.peek()!
self.sendRequest(request: request, completion: { [weak self] in
_ = self?.queue.dequeue()
semaphore.signal()
group.leave()
})
semaphore.wait()
}
group.notify(queue: .global(), execute: { [weak self] in
print("Ending the group")
})
}
}
Lastly, I'll note that:
My queue conforms to the Codable protocol just fine––well, there are no compiler errors, at least. If its implementation beyond that matters, let me know and I'll show it.
The crash occurs a few seconds after I exit the app, while the execution of the processRequests function stops immediately after
I'm using DispatchGroup to perform a task, but group.notify is being called before the task is completed.
My code:
let group = DispatchGroup()
let queueImage = DispatchQueue(label: "com.image")
let queueVideo = DispatchQueue(label: "com.video")
queueImage.async(group: group) {
sleep(2)
print("image")
}
queueVideo.async(group: group) {
sleep(3)
print("video")
}
group.notify(queue: .main) {
print("all finished.")
}
Logs:
all finish.
image
video
Update: The question above actually runs correctly as is (as rmaddy pointed out!)
I'm saving this wrong answer below in case others get confused about DispatchQueue's async(group:) methods behavior, since Apple's swift doc on it is currently lousy.
The group's enter() needs to be called before each call to async(), and then the group's leave() needs to be called at end of each async() block, but within the block. It's basically like a refcount that when it reaches zero (no enters remaining), then the notify block is called.
let group = DispatchGroup()
let queueImage = DispatchQueue(label: "com.image")
let queueVideo = DispatchQueue(label: "com.video")
group.enter()
queueImage.async(group: group) {
sleep(2)
print("image")
group.leave()
}
group.enter()
queueVideo.async(group: group) {
sleep(3)
print("video")
group.leave()
}
group.notify(queue: .main) {
print("all finished.")
}
Generic answer : (Swift 5)
let yourDispatchGroup = DispatchGroup()
yourDispatchGroup.enter()
task1FunctionCall {
yourDispatchGroup.leave() //task 1 complete
}
yourDispatchGroup.enter()
task2FunctionCall {
yourDispatchGroup.leave() //task 2 complete
}
.. ..
yourDispatchGroup.enter()
tasknFunctionCall {
yourDispatchGroup.leave() //task n complete
}
dispatchGroup.notify(queue: .main) {
//This is invoked when all the tasks in the group is completed.
}
If your DispatchGroup is a lazy var, try to not call the notify method inside the initialization code block.
lazy var dispatchGroup: DispatchGroup = {
let dispatchGroup = DispatchGroup()
// not call here dispatchGroup.notify(...
return dispatchGroup
}()
You need to call all the enter methods before the notify method:
dispatchGroup.enter()
dispatchQueue.async(group: dispatchGroup) {
// ...
self.dispatchGroup.leave()
}
dispatchGroup.notify(queue: .main) {
print("all finished.")
}
This is my function using dispatch queue, I would like to cancel it when it is running in the background. How can I do that?
extension DispatchQueue {
static func background(delay: Double = 0.0, background: (()->Void)? = nil, completion: (() -> Void)? = nil) {
DispatchQueue.global(qos: .background).async {
background?()
if let completion = completion {
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: {
completion()
})
}
}
}
}
DispatchQueue.background(background: {
do {
}
catch let error {
// Error handling
}
}, completion:{
})
you can make use of DispatchWorkItem with DispatchGroup.
// create a work item with the custom code
let workItem = DispatchWorkItem {
// Insert your code here
}
//Create dispatch group
let dispatchGroup = DispatchGroup()
// execute the workItem with dispatchGroup
DispatchQueue.global().async(group: dispatchGroup, execute: workItem)
//Handle code after the completion of global queue
dispatchGroup.notify(queue: DispatchQueue.global()) {
print("global queue execution completed")
}
//when the App goes to background cancel the workItem
workItem.cancel()
You should definitely check the Apple Official Documentation concerning :
OperationQueue
BlockOperation
But also this WWDC 2014
Hope it will help you and build up your knowledge base about iOS and higher level API.
I am trying to call 3 functions in order but each function needs to have been completed before the next should run. Each function has a completion handler that calls another function upon completion. After reading lots online about dispatch queues I though this may be the best way to approach it, that's if I am understanding it correctly of course. When I run my code Each function is called in order but not when the previous has been completed. In the first function I am downloading an image from firebase but the second function gets called before the image has downloaded. I've taken out specifics in my code but this is what I have so far.
typealias COMPLETION = () -> ()
let functionOne_completion = {
print("functionOne COMPLETED")
}
let functionTwo_completion = {
print("functionTwo COMPLETED")
}
let functionThree_completion = {
print("functionThree COMPLETED")
}
override func viewDidLoad() {
super.viewDidLoad()
let queue = DispatchQueue(label: "com.myApp.myQueue")
queue.sync {
functionOne(completion: functionOne_completion)
functionTwo(completion: functionTwo_completion)
functionThree(completion: functionThree_completion)
}
func functionOne(completion: #escaping COMPLETION) {
print("functionOne STARTED")
completion()
}
func functionTwo(completion: #escaping COMPLETION) {
print("functionTwo STARTED")
completion()
}
func functionThree(completion: #escaping COMPLETION) {
print("functionThree STARTED")
completion()
}
You could use DispatchGroup
DispatchQueue.global().async {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
functionOne { dispatchGroup.leave() }
dispatchGroup.wait() //Add reasonable timeout
dispatchGroup.enter()
functionTwo { dispatchGroup.leave() }
dispatchGroup.wait()
dispatchGroup.enter()
functionThree { dispatchGroup.leave() }
dispatchGroup.wait()
dispatchGroup.notify(queue: .main) {
//All tasks are completed
}
}
You need to call the second function on the completion of the first.
Something like:
func first(_ completion : #escaping()->()){
print("first")
completion()
}
func second(_ completion : #escaping()->()){
print("second")
}
func third(){
print("third")
}
override func viewDidLoad(){
....
first{
self.second{
self.third()
}
}
}
So when your image download gets finished, inside the completion block where you get the callback of download completion, you should call your second method/block passed as argument which in turn will call your second method.
I've got some issue with CoreData concurrency.
I can't do context.perform while a destination thread is blocked with DispatchGroup.
Here is a simple example which shows the issue:
func upload(objects: [NSManagedObject]) {
let group = DispatchGroup()
for object in objects {
group.enter()
upload(object) {
group.leave()
}
}
group.wait() // current thread is blocked here
someAdditionalWorkToDoInSameThread()
}
func upload(object: NSManagedObject, completion: ()->()) {
let context = object.managedObjectContext
performAlamofireRequest(object) {
context.perform {
// can't reach here because the thread is blocked
update(object)
completion()
}
}
}
Please, help me to reimplement this properly. Thanks.
Using notify on dispatch group instead of wait, should resolve your issues.
Calling wait() blocks current thread for completion of previously submitted work.
notify(queue:execute:) will notify the queue that you passed as argument that the group task has been completed.
func upload(objects: [NSManagedObject], completion: ()->()) {
let group = DispatchGroup()
for object in objects {
group.enter()
upload(object) {
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
completion()
}
}