2010-01-26 5 views
5

Sono di fronte a un problema molto fastidioso. La mia app per iPhone sta caricando i suoi dati da un server di rete. I dati vengono inviati come plist e, se analizzati, devono essere archiviati in SQLite db utilizzando CoreData.Importazione di set di dati di grandi dimensioni su iPhone utilizzando CoreData

Il problema è che in alcuni casi questi set di dati sono troppo grandi (5000+ record) e l'importazione richiede troppo tempo. Di più su questo, quando l'iPhone tenta di sospendere lo schermo, Watchdog uccide l'app perché sta ancora elaborando l'importazione e non risponde fino a 5 secondi, quindi l'importazione non è mai finita.

Ho utilizzato tutte le tecniche consigliate in base all'articolo "Efficiently Importing Data" http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/CoreData/Articles/cdImporting.html e altri documenti a riguardo, ma è ancora terribilmente lento.

Soluzione che sto cercando è lasciare sospendere l'app, ma lasciare eseguire l'importazione in dietro (meglio uno) o per impedire a tutti i tentativi di sospendere l'app. O qualsiasi idea migliore è anche benvenuta.

Tutti i suggerimenti su come superare questi problemi sono molto apprezzati! Grazie

+0

Come si è verificato questo problema? Ho a che fare con un set di dati di dimensioni simili se non più grande e ho bisogno di scaricarlo una volta al giorno dal servizio web. Sto prendendo in considerazione il download di un file .sqlite preparato per la notte rispetto al servizio web attuale. – Augie

+2

Penso che il caricamento di un file sqlite pronto per l'uso sia la migliore soluzione possibile per questi casi, o per lo meno semplice. Altre soluzioni sono troppo complesse e le prestazioni sono comunque scadenti sui dispositivi mobili. – Matthes

risposta

0

C'è un modo per impacchettare i dati prima del tempo, ad esempio durante lo sviluppo? E quando si spinge l'app nel negozio, alcuni dati sono già lì? Questo ridurrà la quantità di dati che devi tirare, aiutando così a risolvere questo problema?

Se i dati sono sensibili al fattore tempo o non pronti o per qualsiasi motivo non è possibile farlo, è possibile comprimere i dati utilizzando la compressione zlib prima di inviarli via rete?

Oppure il problema è che il telefono muore facendo inserti 5K +?

+0

Grazie per la risposta rapida. Sì, il problema è che muore su inserti 5K +. I dati sono compressi dal server, quindi il tempo di download non è il problema. Purtroppo, poiché è un aggiornamento basato sul tempo, non può essere precaricato o memorizzato nella cache. – Matthes

+0

Quindi, per quanto riguarda la rottura dell'inserto, ad esempio, 10x 500 inserti di articoli? Questo in realtà è fatto solo una volta, giusto? Nel primo avvio dell'applicazione? Inoltre, è possibile semplicemente trascinare parti dei dati di cui si ha bisogno, in una discussione diversa. Se i dati sono segmentati sul server, avrai una migliore capacità di identificare il segmento di cui hai bisogno e tirare solo quello? –

4

Invece di inviare file plist sul telefono, è possibile che si desideri inviare file sqlite pronti all'uso. Questo ha molti vantaggi:

  1. alcun bisogno di importare sul telefono
  2. più
  3. compatta

Se si sostituisce sempre tutto il contenuto semplicemente sovrascrivere la memoria permanente nel dispositivo. Altrimenti si potrebbe voler mantenere un array come plist con tutti gli sqlites che hai scaricato e quindi usarlo per aggiungere tutti i negozi a persistentStoreCoordinator.

Bottom line: utilizzare diversi file sqlite precompilati e aggiungerli a persistentStoreCoordinator.

È possibile utilizzare iPhone Simulator per creare tali CoreData-SQLite-Stores o utilizzare un'app Mac standalone. Avrai bisogno di scriverli entrambi.

0

Immagino che non mostri tutti i record 5K al client? Ti consigliamo di fare tutta l'aggregazione di cui hai bisogno sul server, quindi di inviare solo i dati necessari al telefono. Anche se ciò comporta la generazione di alcune visualizzazioni di dati diverse, rimarranno comunque ordini di grandezza più veloci dell'invio (e quindi dell'elaborazione) di tutte quelle righe nell'iPhone.

Si stanno elaborando anche i dati in un thread separato (non evento/ui)?

+0

Il motivo per il quale una tale quantità di dati viene caricata in una sola volta è pre-cache. I dati sono quindi disponibili anche se sei offline, quindi l'app è ancora utilizzabile. Sfortunatamente, questo è il principio base di questa app, quindi non c'è modo di cambiarlo ... – Matthes

2

Ho risolto un problema simile inserendo l'elaborazione di inserimento in un thread in background. Ma prima ho creato un avviso di avanzamento in modo che l'utente non potesse manipolare l'archivio dati mentre stava inserendo le voci.

Questo è fondamentalmente il ViewControllers viewDidLoad

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    NSError *error = nil; 
    if (![[self fetchedResultsController] performFetch:&error]) { 
     NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
     abort(); 
    } 

    // Only insert those not imported, here I know it should be 2006 entries 
    if ([self tableView:nil numberOfRowsInSection:0] != 2006) { 

     // Put up an alert with a progress bar, need to implement 
     [self createProgressionAlertWithMessage:@"Initilizing database"]; 

     // Spawn the insert thread making the app still "live" so it 
     // won't be killed by the OS 
     [NSThread detachNewThreadSelector:@selector(loadInitialDatabase:) 
           toTarget:self 
         withObject:[NSNumber numberWithInt:[self tableView:nil 
               numberOfRowsInSection:0]]]; 
    } 
} 

Il filo di inserimento è stato fatto in questo modo

- (void)loadInitialDatabase:(NSNumber*)number 
{ 
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 

    int done = [number intValue]+1; // How many done so far 

    // I load from a textfile (csv) but imagine you should be able to 
    // understand the process and make it work for your data 
    NSString *file = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] 
               pathForResource:@"filename" 
                 ofType:@"txt"] 
               encoding:NSUTF8StringEncoding 
                error:nil]; 

    NSArray *lines = [file componentsSeparatedByString:@"\n"]; 

    float num = [lines count]; 
    float i = 0; 
    int perc = 0; 

    for (NSString *line in lines) { 
     i += 1.0; 

     if ((int)(i/(num*0.01)) != perc) { 
      // This part updates the alert with a progress bar 
      // setProgressValue: needs to be implemented 
      [self performSelectorOnMainThread:@selector(setProgressValue:) 
            withObject:[NSNumber numberWithFloat:i/num] 
           waitUntilDone:YES]; 
      perc = (int)(i/(num*0.01)); 
     } 

     if (done < i) // keep track of how much done previously 
      [self insertFromLine:line]; // Add to data storage... 

    } 

    progressView = nil; 
    [progressAlert dismissWithClickedButtonIndex:0 animated:YES]; 
    [pool release]; 
} 

E 'un rozzo po' in questo modo, si cerca di init la memorizzazione dei dati da dove si era di se l'utente ha avuto la fortuna di interromperlo le volte precedenti ...

+0

Grazie per tutte le vostre risposte. Tuttavia nessuna delle soluzioni proposte sembra essere quella giusta per me. Innanzitutto, questo è un aggiornamento regolare dei dati, non un'inizializzazione del db una tantum al primo avvio dell'app. Controlla periodicamente i dati in un determinato periodo di tempo. Attualmente, i dati vengono scaricati in formato plist xml e analizzati. Nessun problema a questo punto. Quindi i dati vengono importati in batch di 500 o più record contemporaneamente (provati vari valori per le dimensioni del batch senza alcun effetto significativo). Tutto ciò viene eseguito in background mentre viene mostrata la schermata iniziale con la ruota che gira. Tuttavia, il problema rimane lo stesso :( – Matthes

+0

Così ho provato a mettere la parte import di db su thread separato (non ero consapevole del fatto che è stata eseguita sul thread principale) e mi ha aiutato in modo che l'app non venisse uccisa dal OS quando si passa alla modalità di sospensione come indicato, quindi questa parte è stata risolta, grazie per un suggerimento, tuttavia il tempo necessario per importare una quantità così grande di dati è ancora inaccettabile: ci vogliono circa 5 minuti per completare! – Matthes

4

Innanzitutto, se è possibile impacchettare i dati con l'app sarebbe l'ideale.

Tuttavia, supponendo che non si può fare che poi avrei fatto poi seguito:

  1. volta che i dati vengono scaricati pausa in più file prima importazione.
  2. Importazione su un thread in background, un file alla volta.
  3. Una volta che un file è stato importato e salvato, eliminare il file di importazione.
  4. All'avvio, cercare quei file in attesa di essere elaborati e riprendere da dove si era interrotto.

Idealmente, l'invio dei dati con l'app sarebbe di gran lunga inferiore, ma la seconda soluzione funzionerà e sarà possibile perfezionare i dati durante lo sviluppo.

+0

Grazie Questa è una soluzione abbastanza buona, ma ricorda che ho a che fare con file XML, quindi probabilmente dividere XML in un punto buono (tenerlo valido) richiederebbe un altro tempo di elaborazione, che probabilmente sarebbe anche molto lungo (elaborazione XML su iPhone è molto doloroso e lento, come forse sapete. Quindi, probabilmente non c'è alcuna opzione anche per me :( – Matthes

+0

Dato che i dati arrivano come un plist, inseriscilo in un NSDictionary, spl quel dizionario a parte e lo riscrivi. Sarei molto sorpreso se questo fosse quasi il tempo necessario per analizzare tutti i dati e iniettarli in Core Data. –

0

Qualche possibilità è possibile impostare il lato server per esporre un servizio web RESTful per l'elaborazione dei dati? Ho avuto un problema simile ed è stato in grado di esporre le mie informazioni tramite un webservice RESTful. Ci sono alcune librerie sull'iPhone che rendono la lettura da un webservice così facile. Ho scelto di richiedere JSON dal servizio e ho utilizzato la libreria SBJSON sull'iPhone per ottenere rapidamente i risultati che ho ottenuto e convertirli in dizionari per un facile utilizzo. Ho usato la libreria ASIHTTP per fare le richieste web e fare la coda alle richieste di follow up e farle funzionare in background.

La cosa bella di REST è che è un modo integrato per afferrare batch di informazioni in modo che non sia necessario arbitrariamente capire come suddividere i file che si desidera inserire. Basta impostare quanti record si desidera recuperare e alla richiesta successiva si salta quel numero di record. Non so se questa sia un'opzione per te, quindi non sto entrando in molti esempi di codice in questo momento, ma se è possibile, potrebbe essere un modo semplice per gestirlo.

0

Accettiamo che Riposo (caricamento lazy) non è un'opzione ... Capisco che vuoi replicare. Se il problema di carico è di tipo 'file sempre meno carico in sempre più tempo), poi nel codice pseudo ...

[self sQLdropIndex(OffendingIndexName)] 
[self breathInOverIP]; 
[self breathOutToSQLLite]; 
[self sQLAddIndex(OffendingIndexName)] 

Questo dovrebbe dire un sacco.

1

Ho avuto un problema simile importando molti oggetti in CoreData. Inizialmente stavo facendo un save sul contesto dell'oggetto gestito dopo ogni oggetto che volevo creare inserto &.

Quello che dovresti fare è creare/inizializzare ogni oggetto che vuoi salvare in CoreData, e dopo aver passato in rassegna tutti i dati remoti + creato gli oggetti, fai un contesto oggetto gestito save.

Immagino che si possa considerare questo come fare una transazione in un database SQLite: iniziare la transazione, fare molti inserimenti/aggiornamenti, concludere la transazione.

se questo è ancora troppo lungo, basta avvitare il compito maledettamente ed evitare interazione con l'utente fino a completa

0

Io lavoro su un app che ha regolarmente per elaborare 100K inserti, eliminazioni e aggiornamenti con Core Data. Se si soffoca sugli inserti 5K, è necessario eseguire un'ottimizzazione.

In primo luogo, creare una sottoclasse NSOperation per l'elaborazione dei dati. Sovrascrivi il suo metodo -main per eseguire l'elaborazione. Questo metodo, tuttavia, non è garantito per l'esecuzione sul thread principale. Infatti, il suo scopo è di evitare l'esecuzione di codice costoso sul thread principale che potrebbe influire sull'esperienza utente facendolo congelare grossolanamente. Quindi, all'interno del metodo -main, è necessario creare un altro contesto dell'oggetto gestito che sia il figlio del contesto dell'oggetto gestito del thread principale.

- (void)main 
{ 
    NSManagedObjectContext *ctx = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
    [ctx setPersistentStoreCoordinator:mainManagedObjectContext.persistentStoreCoordinator]; 
    [ctx setUndoManager:nil]; 
    // Do your insertions here! 
    NSError *error = nil; 
    [ctx save:&error]; 
} 

Dato le circostanze, non credo che avete bisogno di un direttore di annullamento. Avere uno incorrerà in una penalità legata alle prestazioni poiché Core Data sta monitorando le tue modifiche.

Utilizzare QUESTO contesto per eseguire tutte le azioni CRUD nel metodo -main, quindi salvare il contesto dell'oggetto gestito. Il contesto dell'oggetto gestito del thread principale deve essere registrato per rispondere a NSNotification denominato NSManagedObjectContextDidSaveNotification. Registrare in questo modo:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mocDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:nil]; 

quindi definire che il selettore:

- (void)mocDidSaveNotification:(NSNotification *)notification 
{ 
    NSManagedObjectContext *ctx = [notification object]; 
    if (ctx == mainManagedObjectContext) return; 
    [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification]; 
} 

Quando tutto questo viene insieme, che vi permetterà di eseguire operazioni di lunga durata su thread in background senza bloccare il thread dell'interfaccia utente. Esistono diverse varianti di questa architettura, ma il tema centrale è questo: l'elaborazione su thread BG, l'unione sul thread principale, l'aggiornamento dell'interfaccia utente. Alcune altre cose da tenere a mente: (1) mantenere un pool di autorelease durante l'elaborazione e scaricarlo ogni tanto per mantenere basso il consumo di memoria. Nel nostro caso, lo facciamo ogni 1000 oggetti. Adatta le tue esigenze, ma tieni presente che il drenaggio può essere costoso a seconda della quantità di memoria richiesta per oggetto, quindi non vuoi farlo troppo spesso. (2) prova a ridurre i tuoi dati al minimo indispensabile per avere un'app funzionale. Riducendo la quantità di dati da analizzare, si riduce la quantità di tempo necessaria per salvarlo. (3) utilizzando questo approccio multithread, è possibile elaborare i dati contemporaneamente. Quindi crea 3-4 istanze della sottoclasse NSOperation, ognuna delle quali elabora solo una parte dei dati in modo che vengano eseguiti simultaneamente, risultando in una minore quantità di tempo reale consumato per l'analisi del set di dati.