Search code examples
iosmultithreadingasynchronouscloudkit

How do I return value correctly from CloudKit completionHandler


My app. saves a record to iCloud with CloudKit. I would like to check for errors and set a flag based on success or failure. I return the flag via the function call but that does not work. The return value is false even when the save is a success and there is no error. It's because the saveHistoryRecordToCloud function returns false before the completion handler sets savedToCloud to true. I understand this has to do with the asynchronous network call but I'm very new to this and clearly don't understand how to handle it. How do I set what saveHistoryRecordToCloud returns accurately? Thanks for the help! The code is below.

    func saveHistoryRecordToCloud(currentWalk: MyCustomTabBarController.Walk ) -> Bool
{
    var savedToCloud = false
    let history = CKRecord(recordType: "History")
    history.setValue(currentWalk.startTime, forKey: "startTime")
    history.setValue(currentWalk.duration, forKey: "duration")

    activityIndicator.startAnimating()
    publicDatabase.save(history) { (savedRecord, error) in
        if error != nil
        {
            print ("Error saving to iCloud " + error.debugDescription)
        } else
        {
            savedToCloud = true
            DispatchQueue.main.async {
                self.activityIndicator.stopAnimating
            }
            print ("Saving History worked!! recordID,recordName = ",savedRecord?.recordID.recordName ?? "" )
        }
    }
    return (savedToCloud)
}

and the call

        tbc!.currentWalk.savedToCloud = saveHistoryRecordToCloud(currentWalk: tbc!.currentWalk)
    print ("tbc!.currentWalk.savedToCloud ", tbc!.currentWalk.savedToCloud)  // always returns false

Solution

  • You're correct about the calling sequence. One approach is to implement "callback" (or "closure") functions. Assume your current code looks something like this (this is pseudo code, I probably wouldn't get the Swift syntax right):

    function1()
    {
         line1
         line2
         tbc!.currentWalk.savedToCloud = saveHistoryRecordToCloud(currentWalk: tbc!.currentWalk)
         print ("tbc!.currentWalk.savedToCloud ", tbc!.currentWalk.savedToCloud)  // always returns false
         line 5
         line 6
    }
    

    Using a callback function, you'd break that code into two functions like so:

    function1()
    {
         line1
         line2
         tbc!.currentWalk.savedToCloud = saveHistoryRecordToCloud(currentWalk: tbc!.currentWalk)
    }
    

    and

    function1_callback(savedToCloud)
    {
         print ("tbc!.currentWalk.savedToCloud ", savedToCloud)  
         line 5
         line 6
    }
    

    Call function1_callback() from the end of your completion handler, like so:

    publicDatabase.save(history) { (savedRecord, error) in
        if error != nil
        {
            print ("Error saving to iCloud " + error.debugDescription)
        } else
        {
            savedToCloud = true
            DispatchQueue.main.async {
                self.activityIndicator.stopAnimating
            }
            print ("Saving History worked!! recordID,recordName = ",savedRecord?.recordID.recordName ?? "" )
    
            /* new callback added here */
            function1_callback(savedToCloud)
        }
    }
    

    The code flow starts with your first function, it calls the saving function, which begins the async saving process and then your code flow ends. When the async save completes, it fires the completion handler. The completion handler calls the callback function, passing in the results of the save operation, and your code flow resumes.