Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fetching CKAsset Image From CloudKit is Very Slow

I am using CloudKit as a server backend for my iOS application. I'm using it to house some relatively static data along with a handful of images(CKAsset). I ran into a problem when the time came for me to actually fetch those assets from the public database. They load at an excruciatingly slow speed.

My use case is to load an image into every cell inside of a collection view. The images are only 200kb in size, but the fetch process took an average of 2.2 seconds for the download to complete and set the image in a cell. For comparison, I took URLs of similar sized stock images and loaded them in using NSURLSession. It took a mere 0.18 - 0.25 seconds for each image to load.

I have tried multiple different ways of downloading the images from CK: direct fetch of the record, query, and operation query. All of them have similar results. I am also dispatching back to the main queue within the completion block prior to setting the image for the cell.

My database is setup to have a primary object with several fields of data. I then setup a backwards reference style system for the photos, where each photo just has a reference to a primary object. That way I can load the photos on demand without bogging down the main data.

It looks something like this:

Primary Object: title: String, startDate: Date

Photo Object: owner: String(reference to primary object), image: Asset

Here is an example request that I tried to directly fetch one of the photos:

let publicDb = CKContainer.defaultContainer().publicCloudDatabase
let configRecordId = CKRecordID(recordName: "e783f542-ec0f-46j4-9e99-b3e3ez505adf")

publicDb.fetchRecordWithID(configRecordId) { (record, error) -> Void in
    dispatch_async(dispatch_get_main_queue()) {
        guard let photoRecord = record else { return }
        guard let asset = photoRecord["image"] as? CKAsset else { return }

        guard let photo = NSData(contentsOfURL: asset.fileURL) else { return }

        let image = UIImage(data: photo)!

        cell.cardImageView.image = image
    }
}

I can't seem to figure out why these image downloads are taking so long, but it's really quite the showstopper if I can't get them to load in a reasonable about of time.

Update: I tried the fetch operation with a smaller image, 23kb. The fetch was faster, anywhere from 0.3 - 1.1 seconds. That's better, but still doesn't meet the expectation that I had for what CloudKit should be able to provide.

like image 417
BlueBear Avatar asked Feb 17 '16 05:02

BlueBear


2 Answers

I am using CKQueryOperation. I found that once I added the following line to my code that downloading CKAssets sped up by about a factor of 5-10x.

    queryOperation.qualityOfService = .UserInteractive

Here is my full code:

func getReportPhotos(report:Report, completionHandler: (report:Report?, error:NSError?) -> ()) {
    let photo : Photo = report.photos![0] as! Photo
    let predicate : NSPredicate = NSPredicate(format: "recordID = %@", CKRecordID(recordName: photo.identifier!))
    let query : CKQuery = CKQuery(recordType: "Photo", predicate: predicate)
    let queryOperation : CKQueryOperation = CKQueryOperation()
    queryOperation.query = query
    queryOperation.resultsLimit = numberOfReportsPerQuery        
    queryOperation.qualityOfService = .UserInteractive
    queryOperation.recordFetchedBlock = { record in
        photo.date = record.objectForKey("date") as? NSDate
        photo.fileType = record.objectForKey("fileType") as? String
        let asset : CKAsset? = record.objectForKey("image") as? CKAsset
        if asset != nil {
            let photoData : NSData? = NSData(contentsOfURL:asset!.fileURL)
            let photo : Photo = report.photos![0] as! Photo
            photo.image = UIImage(data:photoData!)
        }

    }
    queryOperation.queryCompletionBlock = { queryCursor, error in
        dispatch_async(dispatch_get_main_queue(), {
            completionHandler(report: report, error: error)
        })
    }
    publicDatabase?.addOperation(queryOperation)
}
like image 157
Jeff Zacharias Avatar answered Nov 02 '22 16:11

Jeff Zacharias


There seems to be something slowing down your main thread which introduces a delay in executing the capture block of your dispatch_async call. Is it possible that your code calls this record fetching function multiple times in parallel ? This would cause the NSData(contentsOfURL: asset.fileURL) processing to hog the main thread and introduce cumulative delays.

In any case, if only as a good practice, loading the image with NSData should be performed in the background and not on the main thread.

like image 20
Alain T. Avatar answered Nov 02 '22 16:11

Alain T.