1

I know firestore calls are async which means this will not work:

    private func removeUserSavedMomentFromAllUsers(moment: StoryMoment, completion: @escaping () -> Void) {
        guard let savedByUIDs = moment.savedByUIDs else { return }
        guard let momentID = moment.id else { return }
        for id in savedByUIDs {
            self.userInfoCollection.document(id).collection("savedMedias").document(momentID).delete { error in
                if let error = error {
                    print("Error removing user saved moment from UID: \(error)")
                }
            }
        }
    }

Since the loop will continue before the delete call completes (same with get requests). I have used dispatch groups in the past to solve this issue. Heres a working example:

    private func removeUserSavedMomentFromAllUsers(moment: StoryMoment, completion: @escaping () -> Void) {
        guard let savedByUIDs = moment.savedByUIDs else { return }
        guard let momentID = moment.id else { return }
        let disSemaphore = DispatchSemaphore(value: 0)
        let dispatchQueue = DispatchQueue(label: "group 1")
        
        dispatchQueue.async {
            for id in savedByUIDs {
                self.userInfoCollection.document(id).collection("savedMedias").document(momentID).delete { error in
                    if let error = error {
                        print("Error removing user saved moment from UID: \(error)")
                    } else {
                        disSemaphore.signal()
                    }
                }
                disSemaphore.wait()
            }
        }
    }

But those do all the work on the background thread. My question is: How can I use async/await in a for loop where you call firebase docs?

Trevor
  • 580
  • 5
  • 16
  • have you tried searching for info on SO and elsewhere, to answer "..How can I use async/await in a for loop where you call firebase docs?.." For example: https://stackoverflow.com/questions/68149516/swift-async-await-in-for-loop or https://developer.apple.com/documentation/swift/asyncsequence or https://www.hackingwithswift.com/quick-start/concurrency/how-to-loop-over-an-asyncsequence-using-for-await You can also look into using `withTaskGroup(of: ...)`. – workingdog support Ukraine May 14 '22 at 03:28
  • @workingdogsupportUkraine I am having a difficult time using that logic with my specific problem – Trevor May 14 '22 at 05:38
  • Why are you stating the first section of code 'won't work'? In general it's recommended you don't put calls in a tight loop like that but it certainly works for small groups of data. Another option is DispatchGroups but a different implementation than what's in the question. See my answer. – Jay May 14 '22 at 14:48
  • @Jay unfortunately with firestore theres no way to write data to multiple documents with one call, the only solution is to loop every call individually. – Trevor May 14 '22 at 20:16
  • 1
    Glad my answer helped! For clarity, you can group multiple calls into a single [Firestore Transaction](https://firebase.google.com/docs/firestore/manage-data/transactions) *"A batch of writes completes atomically and can write (and delete) multiple documents"* with a limit of 500 at a time. There are also server functions to perform batch deletes as well. Neither of those directly applies to this use case but wanted to add those as additional options for future readers. – Jay May 15 '22 at 14:19

1 Answers1

1

The code in the first part of the question does work - and works fine for small group of data. However, in general it's recommended to not call Firebase functions in tight loops.

While the question mentions DispatchQueues, we use DispatchGroups with .enter and .leave as it's pretty clean.

Given a Firebase structure

sample_data
   a
      key: "value"
   b
      key: "value"
   c
      key: "value"
   d
      key: "value"
   e
      key: "value"
   f
      key: "value"

and suppose we want to delete the d, e, and f documents. Here's the code

func dispatchGroupDelete() {
    let documentsToDelete = ["d", "e", "f"]
    let collection = self.db.collection("sample_data") //self.db points to my Firestore
    let group = DispatchGroup()
    
    for docId in documentsToDelete {
        group.enter()
        let docToDelete = collection.document(docId)
        docToDelete.delete()
        group.leave()
    }
}

While this answer doesn't use async/await, those may not be needed for this use case

If you want to use async/await you try do this

let documentsToDelete = ["d", "e", "f"]
let collection = self.db.collection("sample_data")

for docId in documentsToDelete {
    let docToDelete = collection.document(docId)
    Task {
        do {
            try await docToDelete.delete()
        } catch {
            print("oops")
        }
    }
}
Jay
  • 34,438
  • 18
  • 52
  • 81
  • I think you are correct in saying async/await is not the best solution for this case. DG seems to be a better solution – Trevor May 14 '22 at 20:14