swiftcloudkitcompletion-block

Adding a completion block to a CloudKit function


My ViewController wants to display some data based on a CloudKit query.

My CloudKit code is all in a separate class. That class has a function called loadExpenses() that fetches some Expenses entities from CK.

I want to be able to call loadExpenses() from the VC, so I need a completion block provided by the function to update the UI from the VC.

This is what loadExpenses() looks like:

func loadExpenses() {
    let pred = NSPredicate(value: true)
    let sort = NSSortDescriptor(key: "creationDate", ascending: true)
    let query = CKQuery(recordType: "Expense", predicate: pred)
    query.sortDescriptors = [sort]

    let operation = CKQueryOperation(query: query)
    operation.desiredKeys = ["person", "action", "amount", "timestamp"]
    operation.resultsLimit = 50

    var newExpenses = [Expense]()

    operation.recordFetchedBlock = { (record) in
        let recordID = record.recordID
        let person = record["person"] as! String
        let action = record["action"] as! String
        let amount = record["amount"] as! Double
        let timestamp = record["timestamp"] as! NSDate
        let expense = Expense(person: person, action: action, amount: amount, timestamp: timestamp)
        newExpenses.append(expense)
    }

    // This is the part that needs to be changed
    operation.queryCompletionBlock = { [unowned self] (cursor, error) in
        dispatch_async(dispatch_get_main_queue()) {
            if error == nil {
                self.objects = newExpenses
                self.tableView.reloadData()
                self.refreshRealmDataFromCK()
            } else {
                let ac = UIAlertController(title: "Fetch failed", message: "There was a problem fetching the list of expenses; please try again: \(error!.localizedDescription)", preferredStyle: .Alert)
                ac.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
                self.presentViewController(ac, animated: true, completion: nil)
            }
        }
    }
    CKContainer.defaultContainer().privateCloudDatabase.addOperation(operation)
}

Obviously the last part won't execute, considering all those self.property belong to the VC (I kept them just to show what I need to do in the VC).

As I said, I want to be able to call this function from the VC and get/use a completion block to update those properties. How do I do that?


Solution

  • You need to pass a block as a parameter to the loadExpenses() function. So it should actually be defined something like loadExpenses(completionBlock:([whatever parameters you need in here])->Void).

    Then, you can call the passed completionBlock block (with appropriate parameters) from within the operation.queryCompletionBlock block.

    EDIT:

    So this is not tested at all of course, but you could give it a shot:

    func loadExpenses(completionBlock:([Expense])->Void) {
        let pred = NSPredicate(value: true)
        let sort = NSSortDescriptor(key: "creationDate", ascending: true)
        let query = CKQuery(recordType: "Expense", predicate: pred)
        query.sortDescriptors = [sort]
    
        let operation = CKQueryOperation(query: query)
        operation.desiredKeys = ["person", "action", "amount", "timestamp"]
        operation.resultsLimit = 50
    
        var newExpenses = [Expense]()
    
        operation.recordFetchedBlock = { (record) in
            let recordID = record.recordID
            let person = record["person"] as! String
            let action = record["action"] as! String
            let amount = record["amount"] as! Double
            let timestamp = record["timestamp"] as! NSDate
            let expense = Expense(person: person, action: action, amount: amount, timestamp: timestamp)
            newExpenses.append(expense)
        }
    
        // This is the part that needs to be changed
        operation.queryCompletionBlock = { [unowned self] (cursor, error) in
            completionBlock(newExpenses)
        }
        CKContainer.defaultContainer().privateCloudDatabase.addOperation(operation)
    }
    

    And then call it like this:

    loadExpenses({ (newExpenses:[Expense]) -> Void in {
        dispatch_async(dispatch_get_main_queue()) {
            if error == nil {
                self.objects = newExpenses
                self.tableView.reloadData()
                self.refreshRealmDataFromCK()
            } else {
                let ac = UIAlertController(title: "Fetch failed", message: "There was a problem fetching the list of expenses; please try again: \(error!.localizedDescription)", preferredStyle: .Alert)
                ac.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
                self.presentViewController(ac, animated: true, completion: nil)
            }
        }
    }