Pagine

2014-03-24

Creazione e utilizzo dei file package

Può capitare che nella nostra applicazione, che sia basata su NSDocument o meno, ci sia la necessità di registrare dati in file separati, nascondendoli alla vista del normale utente, in modo che nessuno possa modificarli indipendentemente dall'applicazione.
Per questa funzionalità esistono i package! Nel Finder compaiono come normali singoli file, che possono essere copiati, spostati e cancellati normalmente. In realtà sono cartelle, che il Finder presenta come file perché sa che così deve essere! Esempi? Tutta la suite di iWork salva in questo modo. Selezionando uno di questi file, il tasto destro mostra la voce "mostra contenuto pacchetto".

Per prima cosa, il documento deve essere previsto nel progetto Xcode: se la nostra app è document-based non c'è bisogno di fare nulla, ma se non lo è dobbiamo cliccare sull'icona del progetto e scegliere il tab Info: ci viene mostrata la rappresentazione semplificata del file info.plist della nostra applicazione. Subito sotto, clicchiamo su Document Types e aggiungiamo un documento: è necessario inserire almeno il nome (di nostra scelta) e l'estensione (anche qui siamo liberi, ma non deve essere un'estensione già nota al sistema - l'estensione può essere di qualunque lunghezza - di solito è qualcosa che ricorda il nome del nostro software). Dobbiamo poi scegliere come ruolo (role) Editor e mettiamo la spunta su 'Document is distributed as bundle', che è proprio quello che vogliamo.

Questo è tutto: Xcode porterà nell'Info.plist dell'applicazione questi dati e, dopo averla lanciata la prima volta, le cartelle con questa estensione verranno trattate dal Finder come file singoli. Nella figura sono riportate le impostazioni per la libreria esportata da InerziaThings.

Scrittura

Ora però dobbiamo costruirli. Il tutto viene fatto attraverso l'oggetto NSFileWrapper.
Come esempio, supponiamo di voler inserire in un package alcune immagini e informazioni collegate: l'idea è di scrivere le informazioni in un file plist (già costruito e di nome xmlDoc) e le relative immagini in una cartella, il tutto all'interno del package.
Per prima cosa, trasformiamo il plist in un NSData:

NSData *xmlData = [xmlDoc XMLDataWithOptions:NSXMLNodePrettyPrint];
NSFileWrapper *mainDirectory = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:nil];
[mainDirectory addRegularFileWithContents:xmlData preferredFilename:nomeFile];

Il preferredFileName è proprio il nome con cui comparirà il file plist all'interno del package, a meno che il nome non esista già: ma dato che siamo noi a crearlo, sappiamo cosa ci stiamo mettendo!)
Notiamo che al momento stiamo solo costruendo una struttura in memoria, nulla finora è stato scritto realmente sul disco.
Ora creiamo la cartella che conterrà le immagini con lo stesso metodo di prima, definiamo il nome di questa cartella (imageFolderName) e la aggiungiamo alla struttura del package:

NSFileWrapper *imageDirectory = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:nil];
[imageDirectory setPreferredFilename:imageFolderName];
[mainDirectory addFileWrapper:imageDirectory];

Supponiamo di avere i riferimenti (URL) alle varie immagini in un array imageURLs; creiamo quindi un loop che legga i singoli URL e li aggiunga all'interno della cartella:

for (NSURL *theImage in imageURLs)
{
    NSFileWrapper *wrapImage = [[NSFileWrapper alloc] initWithURL:theImage options:0 error:nil];
  [imageDirectory addFileWrapper:wrapImage];
}

Si è trattato di creare un NSFileWrapper per ogni immagine, partendo dall'URL e aggiungere ognuno di essi al wrapper che rappresenta la cartella.
Ora non ci resta che scrivere effettivamente la struttura su disco e questo viene fatto utilizzando il metodo writeToURL di NSFileWrapper:

NSError *error;
BOOL finito = [mainDirectory writeToURL:exportURL options:0 originalContentsURL:nil error:&error];

dove exportURL rappresenta la locazione sul file system dove andremo a scrivere e sarà stato ottenuto in precedenza tramite un NSSavePanel. È buona norma controllare che non ci siano stati errori, guardando il valore del booleano finito ed eventualmente andando a leggere la descrizione dell'errore contenuta in error.

Lettura

Quando si tratta di leggere un package, dovremo fare lo stesso cammino ma a ritroso: dovremo perciò inizializzare un NSFileWrapper che punti all'URL richiesto (ottenuto da un NSOpenPanel) e chiedere allo stesso oggetto i file wrapper contenuti, tramite il metodo fileWrappers, che fornisce un NSDictionary contenente i riferimenti:

NSError *error;
NSFileWrapper *mainWrapper = [[NSFileWrapper alloc] initWithURL:theURL options:NSFileWrapperReadingImmediate error:&error];
NSDictionary *rootWrappers = [mainWrapper fileWrappers];

Alla fine, dentro rootWrappers avremo i dati necessari per ricostruire la struttura; per prima cosa otteniamo il wrapper della cartella con le immagini, utilizzando come chiave nel NSDictionary quella che avevamo usato durante la sua creazione (cioè il nome) e da questi i wrapper relativi alle immagini; poi da rootWrappers ricaviamo il nome del file plist in cui sono riportati i dati delle immagini:

NSFileWrapper *imagesDir = [rootWrappers objectForKey:imageFolderName];
NSDictionary *allImages = [imagesDir fileWrappers];
NSFileWrapper *imageListWrapper = [rootWrappers objectForKey:nomeFile];
NSData *theData = [imageListWrapper regularFileContents];
NSError *error;
NSXMLDocument *xmlDocument = [[NSXMLDocument alloc] initWithData:theData options:NSXMLDocumentTidyXML error:&error];


//loop sui nodi XML per ottenere i dati delle immagini
//poi per ogni immagine, questa viene costruita leggendo il wrapper:
//nomeImmagine: il nome originale; theURL: la posizione in cui salvare l'immagine
    NSFileWrapper *theImageWrapper = [dict objectForKey:nomeImmagine];
    NSData *theImageData = [theImageWrapper regularFileContents];
    [theImageData writeToURL:theURL atomically:NO];

In questo spezzone abbiamo ottenuto l'oggetto NSData utilizzando il metodo regularFileContents, da cui si ricava il documento XML. Da questo si potranno leggere i dati in esso memorizzati ed utilizzarli per ricreare le immagini originali con i loro dati. Infatti, con le ultime tre righe (che sono all'interno di un loop), ricaviamo il wrapper per ogni immagine, da questo otteniamo i dati come oggetto NSData, che verrà scritto su disco nella posizione voluta tramite l'ultima riga.

A questo punto abbiamo ricostruito tutta la struttura ed ottenuto i dati di partenza.