Swift DispatchQueue with Firebase synchronously - swift

I'm trying to fetch some data from firebase inside a for loop but it doesn't work.
I know I'm supposed to use DispatchQueue, but I can't understand how I'm supposed to use it with Firebase.
I got a loop:
for i in 0..<Exercice.workout.count{
exercice = (Exercice.workout[i] as! [[Any]])[0][0] as! String
print("Exercice: \(exercice)")
self.endDeleteLastSerie(key: key, exercice: exercice, callback: {(status, endTime) in
if status == "success"{
print("do stuff here")
let index = self.getIndexOfExercice(exerciceName: exercice)
print("Index: \(index)")
print("ExerciceName: \(exercice)")
}
}
Inside my function endDeleteLastSerie I'm calling 2 firebase functions
func endDeleteLastSerie(key:String, exercice: String, callback: #escaping(_ status:String, _ endTime: Int)->Void){
FirebaseWorkout.deleteSerie(key: key, exercice: exercice) { (status) in
if status == "success" {
//we set the end time to firebase
FirebaseWorkout.updateEndExercice(exercice: exercice, callback: { (status, endTime) in
if status == "success" {
callback("success", endTime)
}else{
callback("error", endTime)
}
})
}
}
}
**** Example of one of my firebase function ****
static func deleteSerie(key: String, exercice: String, callback: #escaping (_ status: String)->Void){
let uid = FirebaseUsers.User.uid
print("remove")
Database.database().reference().child("users/"+uid+"/workout/"+self.workoutKey+"/exercice/"+exercice+"/series/"+key).removeValue { (error, DatabaseReference) in
if error == nil {
print("removed from firebase")
callback("success")
}else{
callback("error")
}
}
}
But what I'm getting is:
Exercice: Bench Press
remove
Exercice: Pectoral Fly
remove
removed from firebase
removed from firebase
do stuff here
Index: 1
ExerciceName: Pectoral Fly
do stuff here
Index: 1
ExerciceName: Pectoral Fly
I tried to add my for loop inside:
DispatchQueue.main.sync { }
or
DispatchQueue.global().sync(execute: { })
or
var _dispatchQueue:DispatchQueue = DispatchQueue(label: "first", qos: .userInteractive)
then add my for loop inside
self._dispatchQueue.sync { }
But nothing work
How can I solve this ? and get
Exercice: Bench Press
remove
removed from firebase
do stuff here
Index: 0
ExerciceName: Bench Press
Exercice: Pectoral Fly
remove
removed from firebase
do stuff here
Index: 1
ExerciceName: Pectoral Fly

We don't have enough code here to understand portions of the application. However, it doesn't appear to be related to your Firebase code. How is the Exercise.workout array being managed?
In your first code snippet:
for i in 0..<Exercice.workout.count{
exercice = (Exercice.workout[i] as! [[Any]])[0][0] as! String
print("Exercice: \(exercice)")
self.endDeleteLastSerie(key: key, exercice: exercice, callback: {(status, endTime) in
if status == "success"{
print("do stuff here")
let index = self.getIndexOfExercice(exerciceName: exercice)
print("Index: \(index)")
print("ExerciceName: \(exercice)")
}
}
The for-loop is running through the Exercise.workout array and the functions you reference don't appear to be removing items from the array. Of course, that could be elsewhere in the code. Also, we don't know how the getIndexOfExercice function works.
Further, you are dealing with enclosures on different threads so the print statements can happen in various orders. If you want these to happen synchronously, you'll need to add code to do that.
There are many ways of synchronizing threads and there are plenty of examples on StackOverflow or general google searches.
However I don't think this is the crux of your question so if this doesn't help you find the problem, you will need to supply more code around the array being used (and getindex function).

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

Is it possible to use the beginBackgroundTask() API within SwiftUI lifecycle?

I need to run some code when the app is closed to remove the client from a game. To do this I'm wanting to execute a Google Cloud Function for the server to do the cleanup - the function works, I guess similar to this question I just do not have enough time, and I'm running a completion handler so it's not like iOS thinks the function is finished straight away.
I have seen multiple questions on this, many of which are rather old and do not include answers for the SwiftUI Lifecycle. I have seen this exact issue and a potential answer here, however I'm not using the Realtime Database, I'm using Firestore so there is no equivalents for the onDisconnect methods.
I have seen that you can increase the time you need when the application finishes through beginBackgroundTask(expirationHandler:), I just can't find anywhere to state this can be done through SwiftUI Lifecycle, what I have so far:
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willTerminateNotification), perform: { output in
Backend().removeFromGame(gameCode: otp, playerName: "name", completion: { res, error in
if error != nil{
print(error)
}
})
})
The function called is as follows:
func removeFromGame(gameCode: String, playerName: String, completion: #escaping (Bool?, Error?) -> Void){
Functions.functions().httpsCallable("removeFromGame").call(["gameCode": gameCode, "playerName": playerName]){ result, error in
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain{
_ = FunctionsErrorCode(rawValue: error.code)
let errorDesc = error.localizedDescription
_ = error.userInfo[FunctionsErrorDetailsKey]
print(errorDesc)
}
}else{
print("Removed successfully")
}
}
}
I have seen in this Apple doc how to use the API:
func sendDataToServer( data : NSData ) {
// Perform the task on a background queue.
DispatchQueue.global().async {
// Request the task assertion and save the ID.
self.backgroundTaskID = UIApplication.shared.
beginBackgroundTask (withName: "Finish Network Tasks") {
// End the task if time expires.
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = UIBackgroundTaskInvalid
}
// Send the data synchronously.
self.sendAppDataToServer( data: data)
// End the task assertion.
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = UIBackgroundTaskInvalid
}
}
Just cannot seem to implement it correctly within the new way of getting these system notifications?

GeoFire observeReady gets executed prematurely in Swift

In the following function I am am performing an initial geofire search query and wish to process all keys found, append them to an array and send the entire array back.
The issue is that the observeReady block of code is being executed too prematurely and therefore sending an empty array (nothing displays at first load even though there are keys found within the range).
I understand that the observeSingleEvent call is asynchronous and may be causing this behaviour, so my question is, how can I manage this and ensure that the keys are processed prior to executing the handler call within the observeReady block?
func fetchInitialNearbyVenues(deviceLocation: CLLocation, radius: Double, handler: #escaping ([Venue]) -> ()) {
self.venuesArray.removeAll()
var savedByUsers = [String : String]()
let query = self.GEOFIRE_VENUES_LOC.query(at: deviceLocation, withRadius: radius)
query.observe(.keyEntered) { (key: String!, venueLocation: CLLocation!) in
self.REF_VENUES.child(key).observeSingleEvent(of: .value, with: { (snapshot) in
//process snapshot create and append Venue object to array
//...
//...
self.venuesArray.append(venue) //append Venue to array
})//end observeSingleEvent
}//end geofire query observe
query.observeReady {
handler(self.venuesArray) //PROBLEM: This gets executed prematurely thus sending an empty array via handler
}
}//end func
What you're seeing is expected behavior. The observeReady is guaranteed to fire after all the corresponding observe(.keyEntered) have been called. You can verify this with some simple logging statements:
query.observe(.keyEntered) { (key: String!, venueLocation: CLLocation!) in
print(".keyEntered")
}
query.observeReady {
print(".observeReady")
}
When you run this it will print:
.keyEntered
.keyEntered
...
.observeReady
That is in line with how the API is supposed to work. But in the .keyEntered you are loading additional data from Firebase, which happens asynchronously. And those calls may indeed complete after the .observeReady has fired.
So you will need to implement the necessary synchronization yourself. A simple way to detect if you have loaded all the additional data, is to keep a count of all the keys for which you still need to load data. So you +1 that every time you add a key, and -1 every time you've loaded the venue data:
let venuesToLoadCount = 0
query.observe(.keyEntered) { (key: String!, venueLocation: CLLocation!) in
venuesToLoadCount = venuesToLoadCount + 1
self.REF_VENUES.child(key).observeSingleEvent(of: .value, with: { (snapshot) in
venuesToLoadCount = venuesToLoadCount - 1
if venuesToLoadCount == 0 {
print("All done")
}
}
}
query.observeReady {
if venuesToLoadCount == 0 {
print("All done")
}
}

Updating UI after retrieving device settings

I want to do something simple in Swift. I have to retrieve some setting from a device and then initialize some UI controls with those settings. It may take a few seconds to complete the retrieval so I don't want the code to continue until after the retrieval (async).
I have read countless posts on many websites including this one and read many tutorials. None seem to work for me.
Also, in the interest of encapsulation, I want to keep the details within the device object.
When I run the app I see the print from the initializing method before I see the print from the method.
// Initializing method
brightnessLevel = 100
device.WhatIsTheBrightnessLevel(level: &brightnessLevel)
print("The brightness level is \(brightnessLevel)")
// method with the data retrieval code
func WhatIsTheBrightnessLevel(level brightness: inout Int) -> CResults
{
var brightness: Int
var characteristic: HMCharacteristic
var name: String
var results: CResults
var timeout: DispatchTime
var timeoutResult: DispatchTimeoutResult
// Refresh the value by querying the lightbulb
name = m_lightBulbName
characteristic = m_brightnessCharacteristic!
brightness = 100
timeout = DispatchTime.now() + .seconds(CLightBulb.READ_VALUE_TIMEOUT)
timeoutResult = .success
results = CResults()
results.SetResult(code: CResults.code.success)
let dispatchGroup = DispatchGroup()
DispatchQueue.global(qos: .userInteractive).async
{
//let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
characteristic.readValue(completionHandler:
{ (error) in
if error != nil
{
results.SetResult(code: CResults.code.homeKitError)
results.SetHomeKitDescription(text: error!.localizedDescription)
print("Error in reading the brightness level for \(name): \(error!.localizedDescription)")
}
else
{
brightness = characteristic.value as! Int
print("CLightBulb: -->Read the brightness level. It is \(brightness) at " + Date().description(with: Locale.current))
}
dispatchGroup.leave()
})
timeoutResult = dispatchGroup.wait(timeout: timeout)
if (timeoutResult == .timedOut)
{
results.SetResult(code: CResults.code.timedOut)
}
else
{
print("CLightBulb: (After wait) The brightness level is \(brightness) at " + Date().description(with: Locale.current))
self.m_brightnessLevel = brightness
}
}
return(results)
}
Thank you!
If you're going to wrap an async function with your own function, it's generally best to give your wrapper function a completion handler as well. Notice the call to your completion handler. This is where you'd pass the resulting values (i.e. within the closure):
func getBrightness(characteristic: HMCharacteristic, completion: #escaping (Int?, Error?) -> Void) {
characteristic.readValue { (error) in
//Program flows here second
if error == nil {
completion(characteristic.value as? Int, nil)
} else {
completion(nil, error)
}
}
//Program flows here first
}
Then when you call your function, you just need to make sure that you're handling the results within the completion handler (i.e. closure):
getBrightness(characteristic: characteristic) { (value, error) in
//Program flows here second
if error == nil {
if let value = value {
print(value)
}
} else {
print("an error occurred: \(error.debugDescription)")
}
}
//Program flows here first
Always keep in mind that code will flow through before the async function completes. So you have to structure your code so that anything that's depending on the value or error returned, doesn't get executed before completion.

Closures for waiting data from CloudKit

I have a CloudKit database with some data. By pressing a button my app should check for existence of some data in the Database. The problem is that all processes end before my app get the results of its search. I found this useful Answer, where it is said to use Closures.
I tried to follow the same structure but Swift asks me for parameters and I get lost very quick here.
Does someone can please help me? Thanks for any help
func reloadTable() {
self.timePickerView.reloadAllComponents()
}
func getDataFromCloud(completionHandler: #escaping (_ records: [CKRecord]) -> Void) {
print("I begin asking process")
var listOfDates: [CKRecord] = []
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Riservazioni", predicate: predicate)
let queryOperation = CKQueryOperation(query: query)
queryOperation.resultsLimit = 20
queryOperation.recordFetchedBlock = { record in
listOfDates.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
if error != nil {
print("error")
print(error!.localizedDescription)
} else {
print("NO error")
self.Array = listOfDates
completionHandler(listOfDates)
}
}
}
var Array = [CKRecord]()
func generateHourArray() {
print("generate array")
for hour in disponibleHours {
let instance = CKRecord(recordType: orderNumber+hour)
if Array.contains(instance) {
disponibleHours.remove(at: disponibleHours.index(of: hour)!)
}
}
}
func loadData() {
timePickerView.reloadAllComponents()
timePickerView.isHidden = false
}
#IBAction func checkDisponibility(_ sender: Any) {
if self.timePickerView.isHidden == true {
getDataFromCloud{ (records) in
print("gotData")
self.generateHourArray()
self.loadData()
}
print(Array)
}
}
Im struggling to understand your code and where the CloudKit elements fit in to it, so Im going to try and give a generic answer which will hopefully still help you.
Lets start with the function we are going to call to get our CloudKit data, lets say we are fetching a list of people.
func getPeople() {
}
This is simple enough so far, so now lets add the CloudKit code.
func getPeople() {
var listOfPeople: [CKRecord] = [] // A place to store the items as we get them
let query = CKQuery(recordType: "Person", predicate: NSPredicate(value: true))
let queryOperation = CKQueryOperation(query: query)
queryOperation.resultsLimit = 20
// As we get each record, lets store them in the array
queryOperation.recordFetchedBlock = { record in
listOfPeople.append(record)
}
// Have another closure for when the download is complete
queryOperation.queryCompletionBlock = { cursor, error in
if error != nil {
print(error!.localizedDescription)
} else {
// We are done, we will come back to this
}
}
}
Now we have our list of people, but we want to return this once CloudKit is done. As you rightly said, we want to use a closure for this. Lets add one to the function definition.
func getPeople(completionHandler: #escaping (_ records: [CKRecord]) -> Void) {
...
}
This above adds a completion hander closure. The parameters that we are going to pass to the caller are the records, so we add that into the definition. We dont expect anyone to respond to our completion handler, so we expect a return value of Void. You may want a boolean value here as a success message, but this is entirely project dependent.
Now lets tie the whole thing together. On the line I said we would come back to, you can now replace the comment with:
completionHandler(listOfPeople)
This will then send the list of people to the caller as soon as CloudKit is finished. Ive shown an example below of someone calling this function.
getPeople { (records) in
// This code wont run until cloudkit is finished fetching the data!
}
Something to bare in mind, is which thread the CloudKit API runs on. If it runs on a background thread, then the callback will also be on the background thread - so make sure you don't do any UI changes in the completion handler (or move it to the main thread).
There are lots of improvements you could make to this code, and adapt it to your own project, but it should give you a start. Right off the bat, Id image you will want to change the completion handler parameters to a Bool to show whether the data is present or not.
Let me know if you notice any mistakes, or need a little more help.