Search code examples
swiftclosurescloudkit

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)

    }
}

Solution

  • 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.