iosswiftfirebaseperformancemultipath

Swift: Crash when loading a Table many times


I have following issue:

In my App, you have online recipes now imagine a TabViewController. On the first two pages of this TabViewController you have a view displaying recipes stored on the Realtime Database of Firebase. On the third one you have a simple view with some buttons, but no Firebase used and imported. The problem now is, when I slam the Bottom Bar multiple times and therefore switch the TabViewController multiple times in a second the app crashes. This probably because of Firebase reloading everytime since there is a TabViewController change, maybe resulting in a overload.

Now I get following error:

Fatal error: Index out of range: file /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.2.25.8/swift/stdlib/public/core/ContiguousArrayBuffer.swift, line 444
2020-05-22 16:44:28.057640+0200 GestureCook[10451:3103426] Fatal error: Index out of range: file /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.2.25.8/swift/stdlib/public/core/ContiguousArrayBuffer.swift, line 444

It highlights this code let recipe = myRecipes[indexPath.row] with the index out of range. Now how can I either reduce load on the server or avoid this error?

The reason why there is an increased load is probably since I have to fetch multiple recipes from different locations at once like this simplified example:

// dict is a list of recipe IDs
// And the GetRecipeService.getRecipe is a service which gets a recipe using a .observeSingleEvent (this causes these requests)

for child in dict {
                GetRecipeService.getRecipe(recipeUID: child.key) { recipe in
                    self.myRecipes.append(recipe ?? [String:Any]())
                    }
                    DispatchQueue.main.async {
                        self.tableView.reloadData()
                    }
                }
            }

How could I reduce load? Is there something as multipath udpates in Firebase, but just as a get, so I don't have to load 10-20 recipes with a .observeSingleEvent?


Solution

  • First of all the DispatchQueue block is at the wrong place. It must be inside the closure

    GetRecipeService.getRecipe(recipeUID: child.key) { recipe in
       self.myRecipes.append(recipe ?? [String:Any]())
       DispatchQueue.main.async {
          self.tableView.reloadData()
       }
    }
    

    To manage multiple asynchronous requests in a loop there is an API: DispatchGroup

    let group = DispatchGroup()
    for child in dict {
        group.enter()
        GetRecipeService.getRecipe(recipeUID: child.key) { recipe in
            self.myRecipes.append(recipe ?? [String:Any]())
            group.leave()
        }
    }
    
    group.notify(queue: .main) {
        self.tableView.reloadData()
    }