Search code examples
iosswiftuitableviewcloudkit

Cloudkit Fetch very slow


Running the below code to fetch data from Cloudkit, at the moment it is taking a long to populate a tableView, depending on how many results there are, but if there are over 15 results it takes 10 seconds plus. Are they any ways I can speed this up?

This is my fetch func:

func loadData() {
        venues = [CKRecord]()
         let location = locationManager.location

        let radius = CLLocationDistance(500)

        let sort = CKLocationSortDescriptor(key: "Location", relativeLocation: location!)

        let predicate = NSPredicate(format: "distanceToLocation:fromLocation:(%K,%@) < %f", "Location", location!, radius)

        let publicData = CKContainer.defaultContainer().publicCloudDatabase

        let query = CKQuery(recordType: "Venues", predicate: predicate )

        query.sortDescriptors = [sort]

        publicData.performQuery(query, inZoneWithID: nil) { (results:[CKRecord]?, error:NSError?) in
            if let venues = results {
                self.venues = venues
                dispatch_async(dispatch_get_main_queue(), {
                    self.tableView.reloadData()
                    self.refreshControl.endRefreshing()
                    self.tableView.hidden = false
                })
            }
        }
    }

This is my tableView func:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! NearMe2ViewCell

        if venues.count == 0 {


            return cell
        }

        let venue = venues[indexPath.row]




        print(indexPath.row)


        let venueLocation = venue["Location"] as? CLLocation
        let venueTitle = (venue["Name"] as! String)
        let venueImages = venue["VenuePhoto"] as! CKAsset

        let userLocation = locationManager.location
        let distanceBetween: CLLocationDistance = (venueLocation!.distanceFromLocation(userLocation!))
        self.venueDistance = String(format: "%.f", distanceBetween)

        cell.venueDistance?.text = venueDistance
        cell.venueName.text = venueTitle
        cell.venueImage?.image = UIImage(contentsOfFile: venueImages.fileURL.path!)


        return cell


    }

Solution

  • You should search for the record keys first, so a fetchOperation would include this directive.

    fetchOperation.desiredKeys = ["record.recordID.recordName"]
    

    That should be faster. Break your returned keys into the size you can display on the screen and go get them only. After you display them, go get the next batch in background thread, when you got that the next batch on background etc etc etc.

    Should add perhaps, that fetching the asset should be done on a separate thread too if possible, updating the table as you pull in the assets by reloading the table repeatedly.

    Here is method to search and return keys.

     func zap(theUUID:String) {
        var recordID2Zap: String!
        let predicate = NSPredicate(format: "(theUUID = %@)",theUUID)
        let query = CKQuery(recordType: "Blah", predicate: predicate)
        let searchOperation = CKQueryOperation(query: query)
        searchOperation.desiredKeys = ["record.recordID.recordName"]
        searchOperation.recordFetchedBlock = { (record) in
            recordID2Zap = record.recordID.recordName
        }
    
            if error != nil {
                print("ting, busted",error!.localizedDescription)
            } else {
                print("ok zapping")
                if recordID2Zap != nil {
                    self.privateDB.delete(withRecordID: CKRecordID(recordName: recordID2Zap), completionHandler: {recordID, error in
                        NSLog("OK or \(error)")
                    })
                }
            }
    
        }
    
        searchOperation.qualityOfService = .background
    
        privateDB.add(searchOperation)
        theApp.isNetworkActivityIndicatorVisible = true
    }
    
    }
    

    As for your tableview, and images... use the completion in your icloud code to send a notification to the table view.

    database.fetchRecordWithID(CKRecordID(recordName: recordId), completionHandler: {record, error in
        let directDict = ["blah": "whatever"] as [String : String]
    NotificationCenter.default.post(name: Notification.Name("blahDownloaded"), object: nil, userInfo: directDict)
    }
    

    And in the VC you register said notification.

    NotificationCenter.default.addObserver(self, selector: #selector(blahDownloaded), name: Notification.Name("blahDownloaded"), object: nil)
    
    func blahDownloaded(notification: NSNotification) {
         if let userInfo = notification.userInfo as NSDictionary? as? [String: Any] {
    
    //update you cell
    //reload your table
    }
    

    Does that all make sense?