Figuring out the Control Flow for a Method

In the following function, it seems like the flow of events is going crazy! If anyone could help me understand what's going on, I'd really appreciate it!


Instead of printing 1, 2, 3, etc like I would expect, it's printing things in the following order:


1

14

2

3

4

...

12

13

5

6

7

8

...

9

8

...

7

8

...

10

11

5

6

7

8

...


10

11


func fetchRecipes(query: String) {

/

UIApplication.shared.isNetworkActivityIndicatorVisible = true

var finalRecipes = [Recipe]()

let owned = self.loadIngredients()

var count = 0

print("1")


viewModel.recipes(matching: query) { recipes in

print("2")

DispatchQueue.main.async {

print("3")


for item in recipes {

print("4")


self.viewModel.recipe(id: item.recipeId) { recipe in

print("5")

DispatchQueue.main.async {

print("6")

let ingredients = recipe?.ingredients


for ingredient in ingredients! {

print("7")

for thing in owned! {

print("8")


if (ingredient.localizedCaseInsensitiveContains(thing.name)) {

count += 1

print("9")

}

}

}

if true {

print("10")

}

print("11")

count = 0

}

}

}

print("12")


self.insertRecipes(recipes: finalRecipes)


UIApplication.shared.isNetworkActivityIndicatorVisible = false

self.loadingMore = false

}

print("13")

}

print("14")




}


func insertRecipes(recipes: [Recipe]) {

if recipes.count > 0 {

self.tableView.beginUpdates()

viewModel = viewModel.added(recipes: recipes)

var indexPathsToInsert = [IndexPath]()

let start = self.tableView.numberOfRows(inSection: 0)

let end = start + recipes.count - 1

for i in start...end {

indexPathsToInsert += [IndexPath(row: i, section: 0)]

}

self.tableView.insertRows(at: indexPathsToInsert, with: .automatic)

self.tableView.endUpdates()

}

}

}


Thanks so much!

That's because of asynchronism of closure execution.


namely :


You start printing "1"

When you call the closure :

viewModel.recipes(matching: query) { recipes in

print("2") … }


the closure is sent to another thread and execution continues after the closure

Then you print "14"


Then the closure executes and you print 2 to 4


Then you call another closure :

self.viewModel.recipe(id: item.recipeId) { recipe in

print("5") … }


the closure is sent to another thread and execution continues after the closure

Then you print "12" and "13"

Then the closure executes and you print 5 to 8

The rest depends on logic conditions

Thanks Claude31! Do you know if there is any way to access the information in my viewModel so that the code could run linearly?

There are several solutions. You should read thje following tutorial:


h ttps://www.raywenderlich.com/148513/grand-central-dispatch-tutorial-swift-3-part-1


I implemented a very simple semaphore mechanism to wait for an alert to return ; you can try here :


func fetchRecipes(query: String) {
        UIApplication.shared.isNetworkActivityIndicatorVisible = true
        var finalRecipes = [Recipe]()
        let owned = self.loadIngredients()
        var count = 0
        print("1")
        var semaphore = false     // That will be used to wait for completion of closure

        viewModel.recipes(matching: query) { recipes in
            print("2")
          // … following code
          // When ending, set semaphore to true
          semaphore = true
          }
          repeat { }  // Just wait till closure has completed
          while !semaphore
        print("14")
    }

>> Do you know if there is any way to access the information in my viewModel so that the code could run linearly?


I don't understand your question. You asked for the various pieces of your code to run asynchronously (i.e. without regard to order between the pieces), so why are you expecting them to run in source-code order??


          repeat { }  // Just wait till closure has completed 
          while !semaphore


Yikes! Don't do that! 🙂


This isn't waiting, this is looping, aka hogging the CPU and running down the user's battery as fast as possible.

I didn't mean to ask my code to run asynchronously? I just don't know any other way to access the view controller to get 'recipe' and 'recipes.' How do I stop asking it to run asynchronously?

You used DispatchQueue.main.async. This means "run the following closure (scope)" some time in the future (asynchronously) on the main queue/thread. If you don't want asynchronous execution, don't use DispatchQueue.main.async.


Note that "viewModel.recipes" uses a completion handler (closure) that, as Claude says, looks like it is asynchronously executed. So, that entire block of code is going to execute "later" (when the fetched results are available) — you have some asynchronous behavior forced on you. But it's not entirely clear where that's coming from (why your "viewModel" behaves the way it does), so it's a bit difficult to give any advice on this point.

Is there any way to make it execute inserting the finalRecipes before it inserts it? (Without changing any of the completion functions?)

The problem is, we don't know the intended meaning of the APIs you're using. For example, the "recipes(matching:" method appears to be declared something like this:


     func recipes (matching: Query, matchHandler: [Recipe] -> Void)


That is, it executes the handler for each batch of recipes matching the query. We don't know (but presumably you do, or can find out) if it executes the handler synchronously (before returning to the caller), or asynchronously (later, after fetching the recipes from somewhere like a web site or database). If asynchronous, we don't know if the handler is executed on the calling thread, the main thread, or an arbitrary background thread.


If we assume that the handler is executed asynchronously, but we can't be certain of which thread, and some of the stuff you do in your handler needs to be done on the main thread, you can use DispatchQueue.main to get back to the main thread. So, if your code is structured like this:


     …recipes (matching: query) {
          doStuffThatShouldBeOnMainThread () // ugh, we're not on the main thread
          doOtherStuff ()
     }


You can do this:


     …recipes (matching: query) {
          DispatchQueue.main.sync {
               doStuffThatShouldBeOnMainThread () // now we're on the main thread for this
          }
          doOtherStuff ()
     }


Note that this is synchronously on the main thread, which means the calling code waits until the dispatched block executes before continuing at line 05.


However, doing this synchronously is a risky idea, because it's easy to get into a deadlock, with multiple blocks of code each waiting for the other to finish using the main thread. Instead, you can use an asynchronous pattern:


     …recipes (matching: query) {
          DispatchQueue.main.async {
               doStuffThatShouldBeOnMainThread ()
               doOtherStuff ()
          }

     }


Notice that this uses "async" instead of "sync", and moves "doOtherStuff" inside the dispatched block. Also, you should be able to see that this ensures "doOtherStuff" is done after "doStuffThatShouldBeOnMainThread". Your existing (broken) code actually looks like this:


     …recipes (matching: query) {
          DispatchQueue.main.async {
               doStuffThatShouldBeOnMainThread () // now we're on the main thread for this
          }
          doOtherStuff () // um, well …
     }


which will (typically but not necessarily) execute "doOtherStuff" before "doStuffThatShouldBeOnMainThread". That's why your print statements executed out of order.


What you need to do is adopt the sync pattern from the 2nd code fragment, or the async pattern from the 3rd pattern, instead of the broken pattern from the 4th fragment. In your case, you have two instances of the pattern, one from "recipes(matching:…", and the other from "recipe(id:…". They need to be nested, as you have already done.


One final point. If you don't want doOtherStuff to be executed on the main thread, perhaps because it does something time-consuming, you'll need to put it back on a background thread after doStuffThatShouldBeOnMainThread has finished executing. You end up with a pattern like this:


     …recipes (matching: query) {
          DispatchQueue.main.async { // to main thread
               doStuffThatShouldBeOnMainThread ()
               DispatchQueue.global.async { // back to a background thread
                    doOtherStuff ()
               }
          }
     }


But keep in mind that as this kind of structure gets more deeply nested, you have to think carefully about where you put code that depends on an earlier block of code.

I've been playing around with DispatchQueue.main.async/sync, but I'm still not able to make the methods run in the order I need them to. I split up the nested loop to make it simpler, but it's still giving me issues. How can I modify the code below so that it runs the entire first part, then the entire second part, then the entire third part? Adding DispatchQueue.main.sync around parts 1 and 2 didn't seem to do it.



func fetchRecipes(query: String) {

print("method start")

var finalRecipes = [Recipe]()

let owned = self.loadIngredients()

var count = 0

var ownedRecipes = [Recipe]()

/

UIApplication.shared.isNetworkActivityIndicatorVisible = true

//FIRST PART

print("fetching recipes")

print("1")

self.viewModel.recipes(matching: query) { recipes in

print("2")

for item in recipes {

ownedRecipes.append(item)

print("3")

}

}

print("recipes fetched")



//SECOND PART

print("converting recipes")

for item in ownedRecipes {

self.viewModel.recipe(id: item.recipeId) { recipe in

let ingredients = recipe?.ingredients

for ingredient in ingredients! {

for thing in owned! {

if (ingredient.localizedCaseInsensitiveContains(thing.name)) {

count += 1

}

}

}

if true {

finalRecipes.append(item)

}

count = 0

}

}

print("recipes converted")


//THIRD PART

print("done!")

self.insertRecipes(recipes: finalRecipes)

/

UIApplication.shared.isNetworkActivityIndicatorVisible = false

self.loadingMore = false

}


------------------------------------------------------------------------------------------------

***Also, for context, the recipe functions in my view model looks like this:


func recipe(id: String, completion: @escaping (Recipe?) -> Void) {

apiClient.recipe(id: id, completion: completion)

}

func recipes(matching query: String, completion: @escaping ([Recipe]) -> Void) {

apiClient.recipe(matching: query, page: currentSearchPage, completion: completion)

}



And the recipe API call looks like this:



func recipe(matching query: String, page: Int = 1, completion: @escaping ([Recipe]) -> Void) {

let url = buildSearchRequest(query: query, page: page)

session.dataTask(with: url) { data, response, error in

var recipes = [Recipe]()

do {

if let data = data,

let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? JSON,

let results = jsonResult["recipes"] as? [JSON] {

for case let result in results {

if let recipe = Recipe(json: result) {

recipes += [recipe]

}

}

}

} catch {}

completion(recipes)

}.resume()

}

Ignoring threading issues, all you need to do is change the nesting of the various closures:


func fetchRecipes(query: String) {
     print("method start")
     var finalRecipes = [Recipe]()
     let owned = self.loadIngredients()
     var count = 0
     var ownedRecipes = [Recipe]()
     UIApplication.shared.isNetworkActivityIndicatorVisible = true
     //FIRST PART
     print("fetching recipes")
     self.viewModel.recipes(matching: query) { recipes in
          for item in recipes {
               ownedRecipes.append(item)
          }
          print("recipes fetched")
          //SECOND PART
          print("converting recipes")
          for item in ownedRecipes {
               self.viewModel.recipe(id: item.recipeId) { recipe in
                    let ingredients = recipe?.ingredients
                    for ingredient in ingredients! {
                         for thing in owned! {
                              if (ingredient.localizedCaseInsensitiveContains(thing.name)) {
                                   count += 1
                              }
                         }
                    }
                    if true {
                         finalRecipes.append(item)
                    }
                    count = 0
               }
               print("recipes converted")
               //THIRD PART
               print("done!")
               self.insertRecipes(recipes: finalRecipes)
               UIApplication.shared.isNetworkActivityIndicatorVisible = false
               self.loadingMore = false
          }
     }
}


By "ignoring threading issues", I mean that you might have to add code if any parts of this need to be forced to run on the main thread, or a background thread.

Figuring out the Control Flow for a Method
 
 
Q