2013-03-05 22 views
6

Ho un'app basata su UIDocument che utilizza NSFileWrapper s per archiviare i dati. Il wrapper di file "master" contiene molti wrapper di file di directory aggiuntivi, ognuno dei quali rappresenta una pagina diversa del documento.UIDocument & NSFileWrapper - file di grandi dimensioni che richiedono molto tempo per il salvataggio, nonostante le modifiche incrementali

Quando si salva un documento di grandi dimensioni per la quale è stata modificata solo una piccola parte di una pagina, UIDocument trascorre un tempo lungo nel scrivere le modifiche (in writeContents:andAttributes:safelyToURL:forSaveOperation:error:) di sfondo. Sicuramente dovrebbe scrivere solo questa piccola modifica al file wrapper ... cosa ci vuole così tanto tempo?

mio contentsForType:error: di override restituisce un nuovo involucro file di directory con il contenuto del file master involucro (à la WWDC 2012 Session 218 - Using iCloud with UIDocument):

- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError 
{ 
    if (!_fileWrapper) { 
     [self setupEmptyDocument]; 
    } 
    return [[NSFileWrapper alloc] initDirectoryWithFileWrappers:[_fileWrapper fileWrappers]]; 
} 

Ed ecco una bella foto di una traccia dello stack da Time Profiler:

UIDocument slow write stack trace

per inciso, si dice ~ 1.6s in questo thread di lavoro per risparmiare - in fase di esecuzione effettivo questo equiparato a circa 8 secondi.


Edit:

C'è qualche modo per verificare se i wrapper di file richiedono la scrittura su disco o no? Solo così posso confermare che non sto facendo qualcosa di strano come aggiornare ogni sottocartella quando apporto una piccola modifica (anche se sono sicuro di non esserlo ...).


Edit:

ho avuto un ulteriore giocare con l'applicazione CloudNotes del campione, e sembra che NSFileWrapperfa attuare il risparmio incrementale, almeno in questo caso! L'ho provato inizializzando un documento con 100 note, ognuna delle quali conteneva circa 5 MB di dati. Ho fatto una piccola modifica qua e là (un singolo carattere che cambia in una vista testuale indica che il documento ha bisogno di essere salvato), e ho registrato all'incirca quanto tempo ogni salvataggio ha richiesto. Il test è relativamente grezza (ed eseguire sul simulatore), ma i risultati sono stati qualcosa di simile:

  • prima di scrittura: ~ 8000ms
  • 2 ° scrittura: ~ 4000ms
  • 3 ° scrittura: ~ 300ms
  • scrive tutti successive: ~ 40ms

Ovviamente ci sono molti fattori che influenzano il tempo necessario, in particolare dal momento che è il risparmio utilizzando coordinamento file in un thread in background, ma in generale la tendenza sembra sempre di essere questo tipo di exponen decadimento, fino a quando tutte le scritture diventano veramente molto veloci.

Ma sto ancora cercando di capire perché questo non accade nella mia app. Per un documento di più pagine di grandi dimensioni (grande, ma ancora molte volte più piccolo del documento per il test di CloudNotes che ho eseguito sopra), l'utente può attendere molti secondi prima che un documento venga chiuso. Non voglio dover mettere un filatore per qualcosa che dovrebbe essere praticamente istantaneo.

+0

Stai eseguendo un salvataggio automatico o stai salvando tutto in una volta? Stai iniziando il salvataggio dal thread principale? Sostituisci ogni NSFileWrapper o solo quelli che sono effettivamente cambiati? – Luke

+0

Mi affido esclusivamente al salvataggio automatico, quindi i salvataggi sono avviati da UIDocument, facendo la solita cosa di chiamare 'contentsForType: error:' (thread principale) prima di scrivere (thread in background). Sto solo sostituendo i wrapper di file che ho modificato (_quando_ cambiano). Tuttavia, ho notato che sto usando un metodo che guarda tutti i miei wrapper di file di pagina e li ordina in una matrice ordinata. Penso che questo stia scherzando con il pigro caricamento di NSFileWrapper e causando la necessità di scrivere in qualche modo. Sto solo implementando un indice in modo da non doverlo fare e vedrò che differenza fa. – Stuart

+0

No, cambiare solo usando un file indice per tenere traccia delle pagine/numeri di pagina non ha aiutato. Sto ancora cercando di rintracciare cosa potrebbe causare il lento risparmio. – Stuart

risposta

2

NSFileWrapper sta effettivamente caricando l'intero documento in memoria. Quindi con un UIDocument, l'utilizzo di un NSFileWrapper non va bene per documenti di grandi dimensioni. La documentazione ti fa pensare che faccia un risparmio incrementale, ma nel mio caso non sembra farlo.

UIDocument non è limitato a solo NSFileWrapper o NSData. Puoi usare la tua classe personalizzata, devi solo sovrascrivere determinati metodi. Ho finito per scrivere la mia classe di wrapper di file che si riferisce semplicemente ai file su disco e legge/scrive singoli file su richiesta.

Questo è ciò che la mia classe UIDocument assomiglia utilizzando il file personalizzato involucro:

@implementation LSDocument 

- (BOOL)writeContents:(LSFileWrapper *)contents 
     andAttributes:(NSDictionary *)additionalFileAttributes 
      safelyToURL:(NSURL *)url 
    forSaveOperation:(UIDocumentSaveOperation)saveOperation 
       error:(NSError *__autoreleasing *)outError 
{ 
    return [contents writeUpdatesToURL:self.fileURL error:outError]; 
} 

- (BOOL)readFromURL:(NSURL *)url error:(NSError *__autoreleasing *)outError 
{ 
    __block LSFileWrapper *wrapper = [[LSFileWrapper alloc] initWithURL:url isDirectory:NO]; 
    __block BOOL result; 
    dispatch_sync(dispatch_get_main_queue(), ^(void) { 
     result = [self loadFromContents:wrapper 
           ofType:self.fileType 
            error:outError]; 
    }); 
    [wrapper loadCache]; 
    return result; 
} 

@end 

Io uso questo come classe base e sottoclasse per altri progetti. Dovrebbe darti un'idea di cosa devi fare per integrare una classe di wrapper di file personalizzata.

+1

Grazie per aver trovato il tempo di rispondere (entrambe le mie domande correlate!). Ho passato così tanto tempo a cercare nei dettagli di NSFileWrapper, ma ci sono così poche informazioni su di loro. Diverse cose mi confondono: 1) Se i wrapper di file caricano tutti i dati in memoria e non supportano il salvataggio incrementale, qual è il loro scopo? 2) La documentazione non solo "ti fa pensare" che fanno un risparmio incrementale, lo dice a titolo definitivo! Dalla "Guida alla programmazione di app basata su documenti per iOS": "I wrapper di file supportano il salvataggio incrementale, in contrasto con un singolo oggetto dati binario, se un file ... – Stuart

+1

... wrapper contiene i dati del documento, ad esempio, testo e un'immagine - e il testo cambia, solo il file che contiene il testo deve essere scritto su disco. Questa capacità si traduce in prestazioni migliori. " – Stuart

+1

I file NSFileWrapper sono immutabili, quindi penso che possa salvare i wrapper di file che sono stati * sostituiti *. Quindi, se sostituisci un NSFileWrapper anche se è stato modificato, potrebbe salvarlo comunque. Un NSFileWrapper carica tutto in memoria, quindi nel mio caso non era auspicabile - così ho fatto il mio che si riferisce ai file invece che a un BOOL per indicare che volevo mettere in cache determinati file in memoria. Quando aggiorno un file, imposta un ivar "contenuto" con una BOOL "aggiornata". Quando il mio genitore viene salvato, cerca "aggiornato" e salva i file aggiornati e imposta "contenuto" su zero se la "cache" BOOL è falsa. – Luke