swiftfirebasefirebase-realtime-databasefirebase-storageasyncdisplaykit

Index out of Range exception when using firebase database and storage to download data


Here is my method to retrieve an array of user and post objects from the database.

func getRecentPost(start timestamp: Int? = nil, limit: UInt, completionHandler: @escaping ([(Post, UserObject)]) -> Void) {

    var feedQuery = REF_POSTS.queryOrdered(byChild: "timestamp")
    if let latestPostTimestamp = timestamp, latestPostTimestamp > 0 {
        feedQuery = feedQuery.queryStarting(atValue: latestPostTimestamp + 1, childKey: "timestamp").queryLimited(toLast: limit)
    } else {
        feedQuery = feedQuery.queryLimited(toLast: limit)
    }

    // Call Firebase API to retrieve the latest records
    feedQuery.observeSingleEvent(of: .value, with: { (snapshot) in
        let items = snapshot.children.allObjects

        let myGroup = DispatchGroup()

        var results: [(post: Post, user: UserObject)] = []

        for (index, item) in (items as! [DataSnapshot]).enumerated() {
            myGroup.enter()
            Api.Post.observePost(withId: item.key, completion: { (post) in
                Api.User.observeUser(withId: post.uid!, completion: { (user) in
                    results.insert((post, user), at: index) //here is where I get my error -> Array index is out of range
                    myGroup.leave()
                })
            })
        }
        myGroup.notify(queue: .main) {
            results.sort(by: {$0.0.timestamp! > $1.0.timestamp! })
            completionHandler(results)
        }
    })

}

Here is the call to the method from my view controller. I am currently using texture UI to help with a faster smoother UI.

var firstFetch = true
func fetchNewBatchWithContext(_ context: ASBatchContext?) {
    if firstFetch {
        firstFetch = false
        isLoadingPost = true
        print("Begin First Fetch")
        Api.Post.getRecentPost(start: posts.first?.timestamp, limit: 8  ) { (results) in
            if results.count > 0 {
                results.forEach({ (result) in
                    posts.append(result.0)
                    users.append(result.1)
                })
            }
            self.addRowsIntoTableNode(newPhotoCount: results.count)
            print("First Batch Fetched")
            context?.completeBatchFetching(true)
            isLoadingPost = false
            print("First Batch", isLoadingPost)
        }

    } else {
        guard !isLoadingPost else {
            context?.completeBatchFetching(true)
            return
        }
        isLoadingPost = true

        guard let lastPostTimestamp = posts.last?.timestamp else {
            isLoadingPost = false
            return
        }
        Api.Post.getOldPost(start: lastPostTimestamp, limit: 9) { (results) in
            if results.count == 0 {
                return
            }
            for result in results {
                posts.append(result.0)
                users.append(result.1)
            }
            self.addRowsIntoTableNode(newPhotoCount: results.count)
            context?.completeBatchFetching(true)
            isLoadingPost = false
            print("Next Batch", isLoadingPost)

        }
    }


}

In the first section of code, I have debugged to see if I could figure out what is happening. Currently, firebase is returning the correct number of objects that I have limited my query to (8). But, where I have highlighted the error occurring at, the index jumps when it is about to insert the fifth object, index[3] -> 4th object is in array, to index[7]-> 5th object about to be parsed and inserted, when parsing the 5th object.

So instead of going from index[3] to index[4] it jumps to index[7]. Can someone help me understand what is happening and how to fix it?


Solution

  • The for loop has continued on its thread while the observeUser & observePost callbacks are on other threads. Looking at your code, you can probably just get away with appending the object to the results array instead of inserting. This makes sense because you are sorting after the for loop anyway, so why does the order matter?