Patrick Patrick - 2 months ago 6
Swift Question

How can I return value from completion block to use it in other viewController?

I would like to retrieve a value from

queryCompletionBlock
and sent it to another
viewController
.

Here is my code:

func KcalCloudKitData() {
publicDatabase = container.publicCloudDatabase
allRecords = []
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Kcal", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "KcalCoef", ascending: true)]
let queryOperation = CKQueryOperation(query: query)

queryOperation.recordFetchedBlock = {(record: CKRecord) in
self.allRecords.append(record)
}

queryOperation.queryCompletionBlock = {cursor, error in
if error != nil {
print("Failed loading data from iCloud")
//print(error?.localizedFailureReason)
} else {

for value in self.allRecords {
self.kcalUnitValid.append(value.object(forKey: "KcalValidUnit") as! [String])
}
//self.performSegue(withIdentifier: "AppLoaded", sender: nil)
}
}
print(self.kcalUnitValid)
publicDatabase?.add(queryOperation)
}


When i print using code
print(self.kcalUnitValid)
outside the completion block this obtains an empty table. Any solution ?

Thanks in advance.

Answer

queryCompletionBlock is executed asynchronously so by the time print(self.kcalUnitValid) is called queryCompletionBlock wouldn't have finished its execution yet, in other words, the order in how execution happens is like below sequentially:

  • 1.- queryOperation.queryCompletionBlock(..) is called
  • 2.- print(self.kcalUnitValid) is called // prints empty
  • 3.- after some seconds the result of queryOperation.queryCompletionBlock(..) is returned

Code

What you could do it probably is this:

func KcalCloudKitData() {
  // .. code omitted
  queryOperation.queryCompletionBlock = {cursor, error in
    if error != nil {
      // .. code omitted
    } else {
      for value in self.allRecords {
        self.kcalUnitValid.append(value.object(forKey: "KcalValidUnit") as! [String])
      }

      // Segue to another ViewController
      // And also sending kcalUnitValid
      self.performSegue(withIdentifier: "AppLoaded", sender: self.kcalUnitValid)
    }
  }
  // .. code omitted
}

// Actually in this method is when you decide 
// what data you are going to send to the AnotherViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  if segue.identifier == "AppLoaded" {
    let anotherViewController = segue.destination as! AnotherViewController
    anotherViewController.kcalUnitValid = sender as! Array<[String]>
  }
}

Resources:

Watch this video explaning async programming in Swift