2016-02-17 11 views
10

Sto utilizzando CloudKit come back-end del server per la mia applicazione iOS. Lo sto usando per contenere alcuni dati relativamente statici insieme a una manciata di immagini (CKAsset). Mi sono imbattuto in un problema quando è giunto il momento per me di recuperare effettivamente tali risorse dal database pubblico. Caricano a una velocità atrocemente lenta.Il recupero dell'immagine CKAsset da CloudKit è molto lento

Il mio caso d'uso è di caricare un'immagine in ogni cella all'interno di una vista di raccolta. Le immagini hanno una dimensione di soli 200 kB, ma il processo di recupero ha richiesto in media 2,2 secondi per il completamento del download e l'impostazione in una cella. Per fare un confronto, ho preso URL di immagini di dimensioni simili e li ho caricati usando NSURLSession. Ci sono voluti solo 0,18 - 0,25 secondi per ogni immagine da caricare.

Ho provato più modi diversi di scaricare le immagini da CK: recupero diretto del record, query e query di operazione. Tutti hanno risultati simili. Sto anche tornando alla coda principale all'interno del blocco di completamento prima di impostare l'immagine per la cella.

Il mio database è configurato per avere un oggetto primario con diversi campi di dati. Quindi ho impostato un sistema di stile di riferimento all'indietro per le foto, in cui ogni foto ha solo un riferimento a un oggetto primario. In questo modo posso caricare le foto su richiesta senza appesantire i dati principali.

Sembra qualcosa di simile a questo:

oggetto primario: title: String, startDate: Date

Foto Oggetto: owner: String(reference to primary object), image: Asset

Ecco un esempio di richiesta che ho cercato di prendere direttamente una delle foto:

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 
    } 
} 

Non riesco a capire perché questi download di immagini stiano prendendo così tanto tempo, ma è davvero abbastanza stressante se non riesco a farli caricare in un tempo ragionevole.

Aggiornamento: ho provato l'operazione di recupero con un'immagine più piccola, 23kb. Il recupero è stato più veloce, ovunque da 0,3 a 1,1 secondi. È meglio, ma non soddisfa ancora l'aspettativa che ho avuto per ciò che CloudKit dovrebbe essere in grado di fornire.

+0

stai recuperando l'intero record. Ci sono più risorse in quel disco? Potresti limitarlo solo al campo dell'immagine.Oltre a questo, devi solo eseguire l'ultima istruzione in cui aggiorni l'interfaccia utente nella coda principale. –

+0

@EdwinVermeer Il record con la risorsa ha semplicemente due campi, uno è l'immagine, l'altro è un valore stringa con un riferimento al proprietario. So che posso spostare quelle altre linee dal dispacciamento alla coda principale, ma non fa differenza. Il blocco di completamento del recupero è ciò che impiega per sempre per essere chiamato, non le prestazioni di ciò che è all'interno di quel blocco. – Jonathan

+0

Che tipo di rete stai testando? Si consiglia di verificare la priorità dell'operazione, provare anche a recuperare manualmente il record, invece di utilizzare i metodi di convenienza. – mattsven

risposta

2

Sembra esserci qualcosa che rallenta il thread principale che introduce un ritardo nell'esecuzione del blocco di acquisizione della tua chiamata dispatch_async. È possibile che il tuo codice richiami questa funzione di recupero dei record più volte in parallelo? Ciò causerebbe l'elaborazione NSData (contentsOfURL: asset.fileURL) per incappare nella thread principale e introdurre ritardi cumulativi.

In ogni caso, se solo come buona pratica, il caricamento dell'immagine con NSData deve essere eseguito in background e non nel thread principale.

+0

Attualmente sto eseguendo quel codice in 'cellForItemAtIndexPath:', il che significa che viene eseguito più volte. Non riesco a immaginare che il caso d'uso rallenti le cose tanto quanto lo è, soprattutto perché posso fare la stessa cosa con NSURLSession con risultati eccezionali. Non è questo il modo in cui dovrebbe essere usato CloudKit? Ho interrotto le immagini dai miei dati primari in modo da poter caricare le immagini su richiesta piuttosto che in primo piano. – Jonathan

+0

cellForItemAtIndexPath può essere chiamato tempi piuttosto imprevedibili e verrà sempre eseguito sul thread principale. Non è molto sicuro eseguire operazioni di caricamento di dati asincroni. Sono persino sorpreso che tu non stia ottenendo immagini confuse nelle tue celle perché l'oggetto cella che viene catturato nella tua chiamata dispatch_async rischia di essere riutilizzato per un altro elemento durante il ritardo tra il recupero e la visualizzazione. Ad ogni modo, sembra che tu abbia trovato un modo per farlo funzionare, quindi credo che non avrai bisogno di maggiori informazioni su questa domanda. –

4

Sto usando CKQueryOperation. Ho scoperto che una volta aggiunta la seguente riga al mio codice, il download di CKAssets ha accelerato di circa un fattore di 5-10 volte.

queryOperation.qualityOfService = .UserInteractive 

Ecco il mio codice completo:

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) 
} 
+0

Dopo aver apportato questa modifica, ora puoi scaricare un'immagine da 80k in circa 0,1-0,5 secondi? Come hai strutturato i tuoi dati all'interno della dashboard CK? Ha migliorato le mie velocità di circa il doppio. È passato da circa 2.X secondi a 1.0-1.7 secondi. – Jonathan

+0

Scarico 25 record ciascuno con una risorsa immagine media di 90 K in meno di 2 secondi, a volte meno di 1 secondo, quindi meno di 0,08 secondi per record. Ho una buona connessione WiFi, ma ho provato quando ero fuori oggi con LTE e i risultati erano simili. Ogni record ha 14 campi stringa, un campo posizione e un campo data e un campo asset che contiene i dati immagine. Sono ancora in modalità sviluppo e tutte le ricerche, query e ordinamenti sono verificati. –

+0

Questi sono i tipi di velocità che sto cercando. Quindi l'impostazione dei dati ha un singolo record con tutti i diversi campi su di esso? Il mio setup ha un record con tutti i dati String insieme a un riferimento a un record "Foto". Poi, quando voglio recuperare l'immagine, posso recuperarla direttamente. – Jonathan