Async callback for multiple loops in Swift 3 - swift

In one of the project I'm working on, I'm scheduling multiple notifications for different items in my database. Here's some code :
func setupNotifications() {
for medecine in medecineList! {
if !medecine.whichDaysArray.contains(Medecine.Days.EveryDay.rawValue) {
for day in medecine.whichDaysArrayDate {
for fTime in medecine.frequencyTimeDateArray {
self.createNotificationsForUniqueDays(parsedMedecine: medecine, parsedDay: day, parsedFTime: fTime)
}
}
}
else {
for fTime in medecine.frequencyTimeDateArray {
self.createNotificationsForEveryDay(parsedMedecine: medecine, parsedFTime: fTime)
}
}
}
self.callback()
}
func createNotificationsForEveryDay(parsedMedecine: Medecine, parsedFTime: Date) {
DispatchQueue.main.async {
// Create the notification
}
}
As you can see I deliberately put the function that handles creating the notifications on an async thread. The problem is that I'd like to call the method callback() when every async task has finished doing its job. I have no clue how to achieve this.
Any help, is appreciated thanks!

Method createNotificationsForEveryDay should have a block as parameter. In that block I would check to see if I processed the last item in frequencyTimeDateArray and if I did then I would call self.callback().

Related

Swift: Control dependent functions, avoid callback hell

How can I design the following code better? I have the feeling that the code can lead to a callback hell. Every function depends on completion of the previous one.
Current Solution (bad):
#objc func restoreDocuments(UID: UID) {
DispatchQueue.global(qos: .background).async {
//1. Load user details from RemoteServer#1
UserManager.RemoteServer.loadUserFromRemoteServer(userUID: UID) { (userDict) in
//2. After user is loaded save user to local database
UserManager.LocalDB.saveUser(userData: userDict, completion: {
//After User is restored, restore his documents from RemoteServer#2 (IDs provided in userDetails)
let userDocumentsArray = getDocumentIDsFromUser(userUID: UID)
//Loop through array to get every ID
for ID in userDocumentsArray{
//load each document by ID
loadDocumentsRemote(documentID: ID) { (document) in
//Save loaded document
saveDocumentsLocal(document, completion: {
//At the end populate the UI with the restored documents
DispatchQueue.main.async {
populateUI()
}
})
})
}
})
}
}
I would imagine something like the following code. But I don't know how to communicate the different steps among each other. So that task 2 does not start before task 1 is finished.
What I imagine (simplified):
#objc func restoreDocuments(UID: UID) {
//1. Restore User
UserManager.RemoteServer.loadUser(UID){ (user) in }
UserManager.LocalDB.saveUser(user)
// -> WHEN FINISH PROCCED TO STEP 2 🚨
//2. Load Documents
UserManager.LocalDB.getDocumentIDsFromUser( { (IdArray) in
for ID in IdArray {
RemoteServer.DocManager.loadDocument(ID) { (retrievedDocument) in
LocalDB.DocManager.saveDocument(retrievedDocument)
}
}
}
// -> WHEN FINISH PROCCED TO STEP 3 🚨
//3. Finish
DispatchQueue.main.async {
populateUI()
}
}
But how do I do that? And is that a good approach at all 🤔?
Take a look at futures and promises, two related design patterns that address this issue very well. My company uses BrightFutures, a third party library that offers a decent implementation of both.
You can start by extracting the closures into variables:
let onDocumentsSaved: () -> Void = {
DispatchQueue.main.async {
populateUI()
}
}
let onDocumentsLoaded: (Document) -> Void { document in
saveDocumentsLocal(document, completion: onDocumentsSaved)
}
// continue with other closures, in reverse order of execution
That will clear up your indentation and the steps will be clearly visible. If you want to wait for multiple steps (e.g. multiple documents) in one step, you can use DispatchGroup.
Every step can be also easily extracted into a function, or, you can make your class to work as a state machine.
Also, it's a good idea to group connected methods into utility methods, e.g. your load and save can be grouped to one method with a completion handler, for example:
func loadUserAndSave(userId, completion)
func loadDocumentsAndSave(userId, completion)
then your method could be simplified to (pseudocode):
loadUserAndSave(userId) {
loadDocumentsAndSave {
DispatchQueue.main.async {
populateUI()
}
}
}
Which again would be much simpler .

When to call my completion block if I have multiple asynchronous calls

I'm a bit confused on how to use closures or in my case a completion block effectively. In my case, I want to call a block of code when some set of asynchronous calls have completed, to let my caller know if there was an error or success, etc.
So an example of what I'm trying to accomplish might look like the following:
// Caller
updatePost(forUser: user) { (error, user) in
if let error = error {
print(error.description)
}
if let user = user {
print("User was successfully updated")
// Do something with the user...
}
}
public func updatePost(forUser user: User, completion: #escaping (Error?, User?) -> () {
// Not sure at what point, and where to call completion(error, user)
// so that my caller knows the user has finished updating
// Maybe this updates the user in the database
someAsyncCallA { (error)
}
// Maybe this deletes some old records in the database
someAsyncCallB { (error)
}
}
So ideally, I want my completion block to be called when async block B finishes (assuming async block A finished already, I know this is a BAD assumption). But what happens in the case that async block B finishes first and async block A takes a lot longer? If I call my completion after async block B, then my caller thinks that the method has finished.
In a case like this, say I want to tell the user when updating has finished, but I only really know it has finished when both async blocks have finished. How do I tackle this or am I just using closures wrong?
I don't know if your question has been answered. What I think you are looking for is a DispatchGroup.
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
someAsyncCallA(completion: {
dispatchGroup.leave()
})
dispatchGroup.enter()
someAsyncCallB(completion: {
dispatchGroup.leave()
})
dispatchGroup.notify(queue: .main, execute: {
// When you get here, both calls are done and you can do what you want.
})
Really important note: enter() and leave() calls must balance, otherwise you crash with an exception.
try below code:
public func updatePost(forUser user: User, completion: #escaping (Error?, User?) -> () {
// Not sure at what point, and where to call completion(error, user)
// so that my caller knows the user has finished updating
// Maybe this updates the user in the database
someAsyncCallA { (error)
// Maybe this deletes some old records in the database
someAsyncCallB { (error)
completion()
}
}
}
please try updated answer below:
public func updatePost(forUser user: User, completion: #escaping (Error?, User?) -> () {
var isTaskAFinished = false
var isTaskBFinished = false
// Not sure at what point, and where to call completion(error, user)
// so that my caller knows the user has finished updating
// Maybe this updates the user in the database
someAsyncCallA { (error)
// Maybe this deletes some old records in the database
isTaskAFinished = true
if isTaskBFinished{
completion()
}
}
someAsyncCallB { (error)
isTaskBFinished = true
if isTaskAFinished{
completion()
}
}
}

Swift 3 GCD and Parse

I'm beyond baffled how to accomplish this.
I want to call a function (func1) that will call func2 which does some Parse queries and submits them to the database.
Once all those queries have completed and func2 is completely finished running I want to run func3 which will do similar tasks.
I want to then update my tableView once func3 has completely finished running but I'm having no luck working with GCD.
My issue is when I call func1() then wait for the group to finish the tableview reload data function is executed before func3 is executed.
What would be the best way to solve this?
let group = DispatchGroup()
let queue1 = DispatchQueue()
let queue2 = DispatchQueue()
let queue3 = DispatchQueue()
func1(){
queue1.async(group: group){
for i in 0...10 {
func2(i)
}
}
group.notify(queue: queue2){
func3()
}
}
func2(i: Int){
queue2.async(group: group){
// Perform query for value i
PFQuery.findObjectsInBackground {}
}
}
func3(){
queue3.async(group: group){
PFQuery.findObjectsInBackground {}
}
}
func1()
group.notify(queue: queue4){
tableView.reloadData()
}
To simplify:
func1() calls func2() several times in a for loop.
all of the func2() calls must finish before func3() begins
func3() must finish before tableview can reload.
Should you rather create a single method which is like composition for all asynchronous calculations. That makes it easy to have your control. And yes, you should call the notify after your last call only.
func func1(){
queue1.async(group: group){
print("Some work in background queue 1")
}
}
func func2(){
queue2.async(group: group){
print("Some work in background queue 2")
}
}
func func3(){
group.notify(queue: queue3) {
print("Some work in background queue 3")
}
}
func doCompositeOperations() {
func1()
func2()
DispatchQueue.main.async(group: group) {
print("Reloading after all")
}
func3()
}
doCompositeOperations()
I think func1, func2 func3 ... also perform some asynchronous operation in the queue of its own and returns the result in completion handler. I rather prefere enter and leave variant of dispatch group in cases like such where you can have finer control.
Here's my first stab at this, given this information:
func1() calls func2() several times in a for loop.
all of the func2() calls must finish before func3() begins
func3() must finish before tableview can reload.
func1() {
let group = DispatchGroup()
for element in elements {
group.enter()
func2(element, completion: { group.leave() })
}
group.await()
func3(completion: {tableview.reload() })
}
func func1() {
// Call func 2 and give it a block of work to execute when it is done
func2(completionHandler: {
// call func3 and give it a block of work to do when it is done
func3(completionHandler: {
// call back to the main thread to update the table
update_my_table()
}
})
}
func func2(completionHandler: () -> void) {
PFQuery.findObjectsInBackground()
... other work that must be done before func3 ...
// execute work that should be done when func2 completes
completionHandler()
}
func func3(completionHandler: () -> void) {
... do some stuff to prepare table data ...
// execute work that must be done when func 3 completes
completionHandler()
}
func update_my_table() {
DispatchQueue.main.async {
tableView.reloadData()
}
}

Swift closure async order of execution

In my model have function to fetch data which expects completion handler as parameter:
func fetchMostRecent(completion: (sortedSections: [TableItem]) -> ()) {
self.addressBook.loadContacts({
(contacts: [APContact]?, error: NSError?) in
// 1
if let unwrappedContacts = contacts {
for contact in unwrappedContacts {
// handle constacts
...
self.mostRecent.append(...)
}
}
// 2
completion(sortedSections: self.mostRecent)
})
}
It's calling another function which does asynchronous loading of contacts, to which I'm forwarding my completion
The call of fetchMostRecent with completion looks like this:
model.fetchMostRecent({(sortedSections: [TableItem]) in
dispatch_async(dispatch_get_main_queue()) {
// update some UI
self.state = State.Loaded(sortedSections)
self.tableView.reloadData()
}
})
This sometimes it works, but very often the order of execution is not the way as I would expect. Problem is, that sometimes completion() under // 2 is executed before scope of if under // 1 was finished.
Why is that? How can I ensure that execution of // 2 is started after // 1?
A couple of observations:
It will always execute what's at 1 before 2. The only way you'd get the behavior you describe is if you're doing something else inside that for loop that is, itself, asynchronous. And if that were the case, you'd use a dispatch group to solve that (or refactor the code to handle the asynchronous pattern). But without seeing what's in that for loop, it's hard to comment further. The code in the question, alone, should not manifest the problem you describe. It's got to be something else.
Unrelated, you should note that it's a little dangerous to be updating model objects inside your asynchronously executing for loop (assuming it is running on a background thread). It's much safer to update a local variable, and then pass that back via the completion handler, and let the caller take care of dispatching both the model update and the UI updates to the main queue.
In comments, you mention that in the for loop you're doing something asynchronous, and something that must be completed before the completionHandler is called. So you'd use a dispatch group to do ensure this happens only after all the asynchronous tasks are done.
Note, since you're doing something asynchronous inside the for loop, not only do you need to use a dispatch group to trigger the completion of these asynchronous tasks, but you probably also need to create your own synchronization queue (you shouldn't be mutating an array from multiple threads). So, you might create a queue for this.
Pulling this all together, you end up with something like:
func fetchMostRecent(completionHandler: ([TableItem]?) -> ()) {
addressBook.loadContacts { contacts, error in
var sections = [TableItem]()
let group = dispatch_group_create()
let syncQueue = dispatch_queue_create("com.domain.app.sections", nil)
if let unwrappedContacts = contacts {
for contact in unwrappedContacts {
dispatch_group_enter(group)
self.someAsynchronousMethod {
// handle contacts
dispatch_async(syncQueue) {
let something = ...
sections.append(something)
dispatch_group_leave(group)
}
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
self.mostRecent = sections
completionHandler(sections)
}
} else {
completionHandler(nil)
}
}
}
And
model.fetchMostRecent { sortedSections in
guard let sortedSections = sortedSections else {
// handle failure however appropriate for your app
return
}
// update some UI
self.state = State.Loaded(sortedSections)
self.tableView.reloadData()
}
Or, in Swift 3:
func fetchMostRecent(completionHandler: #escaping ([TableItem]?) -> ()) {
addressBook.loadContacts { contacts, error in
var sections = [TableItem]()
let group = DispatchGroup()
let syncQueue = DispatchQueue(label: "com.domain.app.sections")
if let unwrappedContacts = contacts {
for contact in unwrappedContacts {
group.enter()
self.someAsynchronousMethod {
// handle contacts
syncQueue.async {
let something = ...
sections.append(something)
group.leave()
}
}
}
group.notify(queue: .main) {
self.mostRecent = sections
completionHandler(sections)
}
} else {
completionHandler(nil)
}
}
}

How to know when multiple web calls have ended, to call completion

I'm fairly new to Swift and this question is probably really stupid. So bear with me please.
I have a collection of devices that I want to reset, using a Webservice call. Here is what my Function looks like now (no completion yet)
func resetDevice(completion: () -> ()) {
for device in devices {
device.isValid = 0
DeviceManager.instance.updateDevice(device).call { response in
print("device reset")
}
}
}
I'm not quite sure were to call my completion, neither how to be 100% sure all calls have ended. Any help ?
I'd suggest using dispatch groups:
func resetDevice(completion: () -> ()) {
let dispatchGroup = DispatchGroup()
for device in devices {
dispatchGroup.enter()
device.isValid = 0
DeviceManager.instance.updateDevice(device).call { response in
print("device reset")
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: DispatchQueue.main) {
// Some code to execute when all devices have been reset
}
}
Each device enters the group immediately, but doesn't leave the group till the response is received. The notify block at the end isn't called until all objects have left the group.