Swift closure async order of execution - swift

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)
}
}
}

Related

Why is My Code running this way and is There a better way to solve this issue

// In the code below I am trying to return an array from data in firestore, the array always returned empty when I put the handler outside the for loop so I had to use an if statement inside the for loop to get the array containing the data. after using the print statement you see in the code i found out that the compiler is going over the entire function before entering the for loop, (print("5") & (print("6") are the first to run and when I put the handler outside the for it will also be triggered and return an empty array
**
func getMyGames(joinedGamesIDs: [String], handler: #escaping(_ games: [GameViewModal]) -> ()) {
var games = [GameViewModal]()
if !joinedGamesIDs.isEmpty{
for id in joinedGamesIDs {
db.collection("games").document(id).getDocument { (document, error) in
if let document = document, document.exists {
if let game = self.getGameViewModal(document: document){
games.append(game)
print("1")
print(games.count)
}
print("2")
print(games.count)
}
print("3")
print(games.count)
if games.count == (joinedGamesIDs.count){
handler(games)
}
print("4")
print(games.count)
}
}
print("5")
print(games.count)
}
print("6")
print(games.count)
}
**
I've embedded my explanations in the code commentary for easier reading. But the problem you have is that you aren't coordinating these async tasks (the getting of each document). You must coordinate them so when the last one finishes, you can "return" the array from the function. This function doesn't technically "return" anything (except Void) but the completion handler, in a way, "returns" the array which is why I put it in quotes. These semantic details matter and it helps to understand everything better.
func getMyGames(joinedGamesIDs: [String], handler: #escaping (_ games: [GameViewModel]) -> ()) {
guard !joinedGamesIDs.isEmpty else {
// If there is nothing to do, always consider
// calling the handler anyway, with an empty
// array, so the caller isn't left hanging.
return handler([])
}
// Set up a Dispatch Group to coordinate the multiple
// async tasks. Instatiate outside of the loop.
let group = DispatchGroup()
var games: [GameViewModel] = []
for id in joinedGamesIDs {
// Enter the group on each iteration of async work
// to be performed.
group.enter()
db.collection("games").document(id).getDocument { (document, error) in
if let doc = document,
doc.exists,
let game = self.getGameViewModal(document: doc) {
games.append(game)
} else if let error = error {
// Always print errors when in development.
print(error)
}
// No matter what happens inside the iteration,
// whether there was a success in getting the
// document or a failure, always leave the group.
group.leave()
}
}
// Once all of the calls to enter the group are equalled
// by the calls to leave the group, this block is called,
// which is the group's own completion handler. Here is
// where you ultimately call the function's handler and
// return the array.
group.notify(queue: .main) {
handler(games)
}
}

Synchronize nested async network requests inside a while loop by using Semaphores

I have a func that gets a list of Players. When i fetch the players i need only to show those who belongs to the current Team so i am showing only a subset of the original list by filtering them. I don't know in advance, before making the request, how much players belong to the Team selected by the User, so i may need to do additional requests until i can display on the TableView at least 10 rows of Players. The User by pulling up from the bottom of the TableView can request more players to display. To do this i am calling a first async func request which in turn calls, inside a while, another nested async func request. Here a code to give you an idea of what i am trying to do:
let semaphore = DispatchSemaphore(value: 0)
func getTeamPlayersRequest() {
service.getTeamPlayers(...)
{
(result) in
switch result
{
case .success(let playersModel):
if let validCurrentPage = currentPageTmp ,
let validTotalPages = totalPagesTmp ,
let validNextPage = self.getTeamPlayersListNextPage()
{
while self.playersToShowTemp.count < 10 && self.currentPage < validTotalPages
{
self.currentPage = validNextPage //global var
self.fetchMorePlayers()
self.semaphore.wait() //global semaphore
}
}
case .failure(let error):
//some code...
}
})
}
private func fetchMorePlayers(){
// Completion handler of the following function is never called..
service.getTeamPlayers(requestedPage: currentPage, completion: {
(result) in
switch result
{
case .success(let playersModel):
if let validPlayerList = playersList,
let validPlayerListData = validPlayerList.data,
let validTeamModel = self.teamPlayerModel,
let validNextPage = self.getTeamPlayersListNextPage()
{
for player in validPlayerListData
{
if ( validTeamModel.id == player.team?.id)
{
self.playersToShowTemp.append(player)
}
}
}
self.currentPage = validNextPage
self.semaphore.signal() //global semaphore
case .failure(let error):
//some code...
}
}
}
I have tried both with DispatchGroup and Semaphore but i don't get it what i am doing wrong. I debugged the code and saw that the first async call get executed in a different queue (not the main queue) and a different thread. The nested async call getexecuted on a different thread but i don't know if it's the same concurrent queue of the first async call.
The completion handler of thenested call it's never called. Does anyone know why? is the self.semaphore.wait(), even if it get executed after the fetchMorePlayers() return, blocking/preventing the nested async completion handler to be called?
I am noticing through the Debugger that the completion() in the Xcode vars window has the note "swift partial apply forwarder for closure #1"
If we inline the function call in your loop, it looks something like this:
while self.playersToShowTemp.count < 10 && self.currentPage < validTotalPages
{
self.currentPage = validNextPage //global var
nbaService.getTeamPlayers(requestedPage: currentPage, completion: { ... })
self.semaphore.wait() //global semaphore
}
So nbaService.getTeamPlayers schedules a request, probably on the main DispatchQueue and immediately returns. Then you call wait on your semaphore, which blocks, probably before GCD even tries to run the task scheduled by nbaService.getTeamPlayers.
That's a problem on DispatchQueue.main, which is a serial queue. It has to be a serial queue for UI updates to work. What normally happens is on some iteration of the run loop you make a request, and return.. that bubbles back up to the run loop, which checks for more events and queued tasks. In this case, when your completion handler in getTeamPlayersRequest is waiting to be run, the run loop (via GCD) executes it for that iteration. Then you block the main thread, so the run loop can't continue. If you do need to block always do it on a different DispatchQueue, preferably a .concurrent one.
There is sometimes confusion about what .async does. It only means "run this later and right now return control back to the caller". That's all. It does not guarantee that your closure will run concurrently. It merely schedules it to be run later (possibly soon) on whatever DispatchQueue you called it on. If that queue is a serial queue, then it will be queued to run in its turn in that dispatch queue's run loop. If it's a concurrent queue (ie one you specifically set the attributes to include .concurrent). Then it will run, possibly at the same time as other tasks on that same DispatchQueue.
To avoid that instead of using a loop you can use async-chaining.
private func fetchMorePlayers(while condition: #autoclosure #escaping () -> Bool){
guard condition() else { return }
nbaService.getTeamPlayers(requestedPage: currentPage, completion: {
(result) in
switch result
{
case .success(let playersModel):
if let validPlayerList = playersList,
let validPlayerListData = validPlayerList.data,
let validTeamModel = self.teamPlayerModel,
let validNextPage = self.getTeamPlayersListNextPage()
{
for player in validPlayerListData
{
if ( validTeamModel.id == player.team?.id)
{
self.playersToShowTemp.append(player)
}
}
}
self.currentPage = validNextPage
// Chain to next call
self.fetchMorePlayers(while: condition))
case .failure(let error):
//some code...
}
}
}
Then in getTeamPlayersRequest you can do this:
func getTeamPlayersRequest() {
service.getTeamPlayers(...)
{
(result) in
switch result
{
case .success(let playersModel):
if let validCurrentPage = currentPageTmp ,
let validTotalPages = totalPagesTmp ,
let validNextPage = self.getTeamPlayersListNextPage()
{
self.currentPage = validNextPage //global var
self.fetchMorePlayers(while: self.playersToShowTemp.count < 10 && self.currentPage < validTotalPages)
}
case .failure(let error):
//some code...
}
})
}
This avoids the need to block on a semaphore, because each subsequent request happens in the completion handler of the previously completed one. The only issue is if you need for the completion handler in getTeamPlayersRequest to block while the fetchMorePlayers requests are being fetched, because now it won't you can re-introduce the semaphore. In that case the guard statement in fetchMorePlayers becomes:
guard condition() else
{
self.semaphore.signal()
return
}
That way it only signals on the last completion handler in the chain. You may need to block in a different DispatchQueue though. I think if you need to block, you probably have something about your design that needs to be reconsidered.
If you find yourself reaching for semaphores, it is almost always a mistake. Semaphores are inefficient at best, and introduce deadlock risks if misused. Semaphores should generally be avoided. (Don't get me wrong: Semaphores can be useful in some very narrow use cases, but this is not one of them.)
Use asynchronous patterns. One simple approach might be to recursively call the routine, calling the completion handler when done:
func startFetching(#escaping completion: () -> Void) {
fetchPlayers(page: 0, completion: completion)
}
private func fetchPlayers(page: Int, #escaping completion: () -> Void) {
// prepare request
// now perform request
performRequest(...) { ...
if let error = error {
completion()
return
}
...
if doesNeedMorePlayers {
fetchPlayers(page: page + 1, completion: completion)
} else {
completion()
}
}
}
Personally, I might probably add another closure to emit the players retrieved as we go along, e.g. like, if not actually, a Combine Publisher. Or if you want to update the UI all at once at the very end, just pass the players retrieved thus far as additional parameter in this recursive routine and pass the whole array back in the completion handler. But avoid globals or other state properties.
But the broader idea is to scrupulously avoid semaphores and instead embrace asynchronous patterns.

using dispatch group in multi for loop with urlsession tasks

I have using a dispatch group wait() that block my a for loop from completing the code until a set of urlsession tasks (in another loop with completion handler) to be completed before appending new element to my array
the current code will finish the first loop before the second loop of urlClass.selectfoodURL is completed
I want to append the array in meal history after my urlfood for loop is completed
on of the problem in my approach of using dispatch groups is the wait(), when my select food is called the urlsession stuck and doesn’t complete with group.wait
func userSnackHistoryArray() {
let group = DispatchGroup()
let Arrays // array of dictionary
for array in Arrays {
var generateMeal = MealDetails() // struct type
do {
let aa = try JSONDecoder().decode(userSnack.self, from: array)
generateMeal.names = convertToJsonFile.type
for name in generateMeal.names!{
group.enter()
urlClass.selectfoodURL(foodName: name){ success in
generateMeal.units!.append(allVariables.selectedUnit)
group.leave()
}
}
// my select food is called but the urlsession stuck and doesnt complete with group.wait is active
// group.wait()
mealHistory.append(generateMeal)
} catch { }
}
group.notify(queue: .main){
print("complete")
}
}
I have shortened my code to focus on the problem ,, I can split my code into two functions and solve the problem , but I want to use only one function
any suggestions or ideas ?
Rather than waiting, you should just create a local array of values to be added, and then add them when it’s done:
func retrieveSnacks() {
var snacksToAdd: [Snack] = []
let group = DispatchGroup()
...
for url in urls {
group.enter()
fetchSnack(with: url) { result in
dispatchPrecondition(condition: .onQueue(.main)) // note, I’m assuming that this closure is running on the main queue; if not, dispatch this appending of snacks (and `leave` call) to the main queue
if case .success(let snack) = result {
snacksToAdd.append(snack)
}
group.leave()
}
}
// when all the `leave` calls are called, only then append the results
group.notify(queue: .main) {
self.snacks += snacksToAdd
// trigger UI update, or whatever, here
}
}
Note, the above does not assure that the objects are added in the original order. If you need that, you can use a dictionary to build the temporary results and then append the results in sorted order:
func retrieveSnacks() {
var snacksToAdd: [URL: Snack] = [:]
let group = DispatchGroup()
...
for url in urls {
group.enter()
fetchSnack(with: url) { result in
if case .success(let snack) = result {
snacksToAdd[url] = snack
}
group.leave()
}
}
group.notify(queue: .main) {
let sortedSnacks = urls.compactMap { snacksToAdd[$0] }
self.snacks += sortedSnacks
// trigger UI update, or whatever, here
}
}
Finally, I might suggest adopting a completion handler pattern:
func retrieveSnacks(completion: #escaping ([Snack]) -> Void) {
var snacksToAdd: [URL: Snack] = [:]
let group = DispatchGroup()
...
for url in urls {
group.enter()
fetchSnack(with: url) { result in
if case .success(let snack) = result {
snacksToAdd[url] = snack
}
group.leave()
}
}
group.notify(queue: .main) {
let sortedSnacks = urls.compactMap { snacksToAdd[$0] }
completion(sortedSnacks)
}
}
retrieveSnacks { addedSnacks in
self.snacks += addedSnacks
// update UI here
}
This pattern ensures that you don’t entangle your network-related code with your UI code.
I apologize that the above is somewhat refactored from your code snippet, but there wasn’t enough there for me to illustrate what precisely it would look like. But hopefully the above illustrates the pattern and you can see how you’d apply it to your code base. So, don’t get lost in the details, but focus on the basic pattern of building records to be added in a local variable and only update the final results in the .notify block.
FWIW, this is the method signature for the method that the above snippets are using to asynchronously fetch the objects in question.
func fetchSnack(with url: URL, completion: #escaping (Result<Snack, Error>) -> Void) {
...
// if async fetch not successful
DispatchQueue.main.async {
completion(.failure(error))
}
// if successful
DispatchQueue.main.async {
completion(.success(snack))
}
}

DispatchGroup Not notifying 'Main" in Swift

I'm trying to use Dispatch group to notify me when a task is finished running. I've written a simple pseudo of what I'm trying to accomplish. For some reason my notify function gets called first.
class Main {
let cats = Cats()
let all = ALL.singleton
viewdidLoad(){
cats.makeAllCall
all.dis()
}
}
class Cats {
let dispatch = DispatchGroup()
let all = ALL.singleton
func makeAllCall(){
for i in 1...10{
all.callInfo()
print("hello")
}
}
}
class ALL {
static let singleton = ALL()
let dispatch = DispatchGroup()
func dis(){
dispatch.notify(.main){
print("working")
}
}
func callInfo(){
dispatch.enter()
Alamofire.request("url", headers: headers).responseJSON { response in
if response.result.isSuccess{
completion(JSON(response.result.value!))
}else{
print("Binance - Couldn't import Request: Please check your internet connection")
}
}
dispatch.leave()
}
}
You have not understood how dispatch groups work. You are calling dis(), apparently in the belief that dispatch.notify is something that you call. It isn't. It is called for you when every enter has been balanced by a leave. A typical structure looks like this pseudo-code:
let group = DispatchGroup()
// here we go...
group.enter()
_queue1_.async {
// ... do task here ...
group.leave()
}
group.enter()
_queue2_.async {
// ... do task here ...
group.leave()
}
group.enter()
_queue3_.async {
// ... do task here ...
group.leave()
}
// ... more as needed ...
group.notify(queue: DispatchQueue.main) {
// finished!
}
You need to get rid of this bizarre class structure and put everything — the dispatch group, the enter and leave calls, and the notify block — together in one place. If you don't want to do that, then this is not a good use of a dispatch group (perhaps what you wanted was something like Operation and OperationQueue).

Fetching reminders from calendars in Swift

What's the thread-safe way of fetching Reminders from various calendars? I'm just trying to count all reminders and print them. The printing works but the counting doesn't. Is there a race-condition because fetching reminders is asynchronous?
func loadFromCalendars(cals: [EKCalendar], completed: (NSError?)->()) {
// STEP 1 OF CREATING AN OVERALL COMPLETION BLOCK: Create a dispatch group.
let loadCalendarServiceGroup: dispatch_group_t = dispatch_group_create()
// Define errors to be processed when everything is complete.
// One error per service; in this example we'll have two
let configError: NSError? = nil
let preferenceError: NSError? = nil
var reminderCounter = 0
let eventStore : EKEventStore = EKEventStore()
eventStore.requestAccessToEntityType(EKEntityType.Event, completion: {
granted, error in
if (granted) && (error == nil) {
print("granted \(granted)")
print("error \(error)")
}
})
// Go through calendars.
for cal in cals {
let remindersPredicate = eventStore.predicateForRemindersInCalendars([cal])
// STEP 2 OF CREATING AN OVERALL COMPLETION BLOCK: Adding tasks to a dispatch group
dispatch_group_enter(loadCalendarServiceGroup)
eventStore.fetchRemindersMatchingPredicate(remindersPredicate) {
// MARK: Begininning of thread
reminders in
_ = (reminders!).map {
// TRYING TO COUNT HERE THE REMINDERS. ALWAYS PRINTS 0!
reminder -> EKReminder in
print(reminder.title)
reminderCounter += 1
return reminder
}
dispatch_async(dispatch_get_main_queue()) {
self.sendChangedNotification() // refreshes the UI
}
}
// STEP 3 OF CREATING AN OVERALL COMPLETION BLOCK: Leave dispatch group. This must be done at the end of the completion block.
dispatch_group_leave(loadCalendarServiceGroup)
// MARK: End of thread
}
// STEP 4 OF CREATING AN OVERALL COMPLETION BLOCK: Acting when the group is finished
dispatch_group_notify(loadCalendarServiceGroup, dispatch_get_main_queue(), {
print("************ reminder count: \(reminderCounter) ****************")
// Assess any errors
var overallError: NSError? = nil;
if configError != nil || preferenceError != nil {
// Either make a new error or assign one of them to the overall error. Use '??', which is the "nil Coalescing Operator". It's syntactic sugar for the longer expression:
// overallError = configError != nil ? configError : preferenceError
overallError = configError ?? preferenceError
} // Now call the final completion block
// Call the completed function passed to loadCalendarHelper. This will contain the stuff that I want executed in the end.
completed(overallError)
})
}
EDIT
Thanks for the great tips, jtbandes! I simplified my code (a lot!) One question - I'm chaining some functions that change the resulting data structure. How can I make in the below code groupArrayBy() thread-safe?
public extension SequenceType {
/// Categorises elements of self into a dictionary, with the keys given by keyFunc
func groupArrayBy<U : Hashable>(#noescape keyFunc: Generator.Element -> U) -> [U:[Generator.Element]] {
var dict: [U:[Generator.Element]] = [:]
for el in self {
let key = keyFunc(el)
if case nil = dict[key]?.append(el) { dict[key] = [el] }
}
return dict
}
}
func loadFromCalendars(cals: [EKCalendar], completed: (NSError?)->()) {
let configError: NSError? = nil
let preferenceError: NSError? = nil
withEstore { // retrieves the EKEventStore
estore in
let predicate = estore.predicateForRemindersInCalendars(cals)
estore.fetchRemindersMatchingPredicate(predicate) { reminders in
print("Number of reminders: \(reminders?.count ?? 0)") // Prints correct result
let list = (reminders!).map {
// this map still works, it seems thread-safe
reminder -> ReminderWrapper in
return ReminderWrapper(reminder: reminder) // This still works. ReminderWrapper is just a wrapper class. Not very interesting...
}.groupArrayBy { $0.reminder.calendar } // ERROR: groupArrayBy doesn't seem to be thread-safe!
print("Number of reminders: \(Array(list.values).count)") // Prints a too low count. Proves that groupArrayBy isn't thread-safe.
dispatch_async(dispatch_get_main_queue()) {
self.sendChangedNotification() // refreshes the UI
completed(configError ?? preferenceError)
}
}
}
}
A few changes should be made to this code:
dispatch_group_leave(loadCalendarServiceGroup) must be inside the fetchRemindersMatchingPredicate block. Otherwise, the block you passed to dispatch_group_notify will execute before the fetches are finished, which entirely defeats the purpose of using a group.
The requestAccessToEntityType call is also asynchronous, but your code simply continues after starting the access request, without waiting for it to finish. You might want to chain your completion blocks together.
You're requesting access to the .Event type, but you probably want .Reminder.
reminderCounter += 1 is not thread-safe. You might want to dispatch_async onto a serial queue before changing the counter (so there is no contention between threads), or you could use the OSAtomicAdd family of functions.
Instead of _ = (reminders!).map { reminder in ... }, I would recommend you just use for reminder in reminders { ... }.
However…
I think what you're doing is overly complicated.
Notice that predicateForRemindersInCalendars takes an array of calendars. You can simply pass all your calendars, cals, to get a single predicate encompassing all of them, and run a single query:
let predicate = eventStore.predicateForRemindersInCalendars(cals)
eventStore.fetchRemindersMatchingPredicate(predicate) { reminders in
print("Number of reminders: \(reminders?.count ?? 0)")
dispatch_async(dispatch_get_main_queue()) {
self.sendChangedNotification() // refreshes the UI
completed(configError ?? preferenceError)
}
}