Compatibile con Xcode 8

NSUserDefaults in Swift. Salvare piccole quantità d’informazioni

ENTRA NEL NOSTRO GRUPPO FACEBOOK

Spesso ti capiterà di dover salvare piccole quantità di informazioni dell’utente e di non voler utilizzare sistemi complicati come potrebbero essere il CoreData, i file Plist ecc. Per questo motivo, un sistema semplice e al tempo stesso potente, risulta essere l’NSUserDefault.

L’NSUserDefaults è una classe che permette di interagire con un “Database” (in realtà è un file Plist binario) persistente creato appositamente per ogni applicazione. Persistente significa che, i dati che salverai, continueranno ad esistere anche dopo la chiusura dell’app o aggiornamento.

L’NSUserDefaults, ascoltami bene, nasce esclusivamente per la memorizzazione di piccole quantità di informazioni. Se vuoi una misura delle quantità massima, si consiglia, di non superare il centinaio di KB (kilobyte). Anche se un limite reale non è realmente fissato, solamente per tvOS viene fissato a 500kb, la community dei sviluppatori concorda sul non superare tali limiti.

Perché così poco?

Perché l’NSUserDefaults è una classe Singleton che, oltre a salvare le informazioni in un database basato su file binary plist, riesce a fornirti i dati quasi istantaneamente. In pratica, quando salvi un’informazione con l’NSUserDefaults in Swift viene archiviata e contemporaneamente conservata in memoria RAM per evitare di recuperarla dal DB ogni volta che ti serve (quello che comunemente viene chiamata Cache).

Il problema ricade in questa duplice natura dell’NSUserDefaults. Da un lato salva in in un database che possiede l’utente e, contemporaneamente, tiene questi dati in memoria RAM. In più, il fatto che è una classe Singleton, ogni richiamo dell’oggetto richiamerà tutti i dati salvati in memoria (anche quelli che, paradossalmente, utilizzerai una sola volta in tutta la tua applicazione).

Devi immaginare l’NSUserDefaults come un dizionario chiave-valore. Ogni informazione che vorrai salvare dovrà essere associata ad una chiave univoca e statica, cioè che non cambia nel tempo. Per farti un esempio, immagina di avere una pagina delle impostazioni. In questo ViewController delle impostazioni, l’utente può selezionare un colore custom per lo sfondo della tua applicazione.

Con l’NSUserDefaults potrai salvare questo valore, che sarà composto dalla componente RGB, all’interno di una chiave che potrai chiamare “userBackgroundColor” (il nome lo decidi tu). In questo modo, quando l’applicazione dovrà recuperare le impostazioni dell’utente e il colore custom, richiamerà questa chiave dall’NSUserDefaults.

Anche se il tutorial è abbastanza semplice. Se sei alle prime armi ti consiglio di seguire un percorso più lineare e di formare ottime basi del linguaggio Swift

Premessa fatta! Pronto a vedere l’NSUserDefaults in Swift?

Scrivere un’informazione

Crea un normalissimo progetto Single View Application per iOS e spostati nello storyboard. Aggiungi due textField ed un bottone. Nella prima, l’utente, inserirà il nome dell’utente e, nella seconda, l’età. Il bottone salverà i dati nell’NSUserDefaults.

Questo è un progetto a scopo didattico. Informazioni del genere non andrebbero salvate nell’NSUserDefaults in quanto potrebbero essere incastonate dentro una classe e, di conseguenza, necessiterebbero di sistemi leggermente più avanzati (ma tutto dipende dal tipo di applicazione che stai realizzando):

Schermata 2016-02-22 alle 00.35.10

Nella IBAction associata al bottone aggiungi il codice di controllo sul testo delle due textField:

Per recuperare lo spazio utente in cui salvare i dati, cioè l’User Defaults, basta utilizzare la class func standardUserDefaults() della classe NSUserDefaults. Essendo una class func restituisce l’oggetto singleton della classe NSUserDefaults.

Quindi, subito dopo il recupero del nome e dell’età, aggiungi:

Adesso che hai il riferimento all’NSUserDefaults dell’utente, puoi salvargli dentro le informazioni. Ogni informazione che vorrai salvare, come già ribadito, deve essere associata ad una chiave di tipo String univoca. Questo vuol dire che una volta che memorizzerai una chiave non potrai modificarla.

I metodi per il salvataggio dei tipi di dato prevedono due parametri. Il primo è l’informazione da salvare ed il secondo, chiamato forKey, è la String a cui verrà associato questo dato:

  • setBool(_ :Bool, forKey: String): Salva un valore booleano associato ad una chiave identificativa (forKey).
  • setInteger(_ :Int, forKey: String): Salva un valore intero.
  • setDouble(_ :Double, forKey: String): Salva un valore Double.
  • setFloat(_ :Float, forKey: String): Salva un valore Float.

Dove sono le String? i vettori ecc?

Per poter salvare gli altri tipi di dato bisogna utilizzare il metodo setObject(_: AnyObject?, forKey: String) che, come primo parametro, accetta un AnyObject. Quindi, per esempio, è possibile fare la seguente:

Anche se il parametro accetta un AnyObject, l’NSUserDefaults ti permette di salvare solamente: Array, Dizionari e Stringhe. Un caso particolare sono gli NSDate (il tipo di dato delle date del calendario) e l’NSData (un tipo di dato per il salvataggio di qualsiasi dato). Pur dandoti la possibilità di salvare un NSData, ciò non vuol dire che potrai salvare qualsiasi cosa. Infatti, ribadisco che, per salvare grandi quantità d’informazioni come foto, oggetti intesi come istanze di classe, bisogna utilizzare altri sistemi di memorizzazione come il CoreData o il CloudKit.

La funzione setObject è relativamente nuova. É stata introdotta con iOS 8 e, sicuramente, con le prossime release il campo di dati permessi verrà ampliato.

Fatto questo piccolo excursus sui metodi, torniamo al nostro progetto.

Proviamo a memorizzare i dati dell’utente. Quindi, aggiungi nell’NSUserDefaults la stringa nome associata ad una chiave chiamata “UserName” e la stringa età, che trasformerai in Int, nella chiave “UserAge”:

Avvia l’applicazione, scrivi un nome ed un’età nei due campi e premi il tasto salva. Se hai inserito i valori correttamente, i dati verranno salvati nell’NSUserDefaults.

Adesso ti manca solamente il recupero.

Recuperare un’informazione

Recuperare un oggetto dall’NSUserDefaults in Swift è altrettanto semplice quanto salvarlo. C’è solamente un piccolo appunto da fare. Dato che le informazioni vengono recuperate per chiave e si utilizzano dei metodi appositi, come quelli visti per il setInt, setDouble ecc, bisogna stare attenti alla funzione da utilizzare.

S si utilizza uno dei metodi per la lettura, per esempio e si passa una chiave errata o si confonde un metodo per un altro, vengono restituiti dei valori che potrebbero compromettere la tua applicazione.

Prima di farti un esempio, questi sono i metodi da utilizzare:

  • integerForKey(_ :String): Restituisce il valore intero associato alla chiave passata come parametro. Oppure 0 se non esiste il valore o la chiave è errata/non esiste.
  • boolForKey(_: String): Restituisce il valore booleano associato alla chiave. Restituisce false in caso d’errore.
  • doubleForKey(_: String): Restituisce il valore double associato alla chiave. Restituisce 0.0 in caso d’errore.
  • floatForKey(_: String): Restituisce il valore float associato alla chiave. Restituisce 0.0 in caso d’errore.
  • objectForKey(_: String): Restituisce l’AnyObject? associato alla chiave. Restituisce nil in caso d’errore.

I valori di ritorno restituiti in caso d’errore, proprio perché sono valori possibili e non interpretabili, potrebbero portarti a non accorgerti di un probabile problema.

Infatti, immagina di aver assegnato un booleano all’utente per l’utilizzo o meno del Touch ID al posto della password. L’utente passa a true un’impostazione dell’app e questo valore viene correttamente salvato nell’UserDefaults. Se, per errore, fai un boolForKey di una chiave errata, di una che non esiste o di un valore ancora non assegnato verrà sempre restituito false.

Ma false è un valore ammissibile perché può darsi che un utente non passi mai a true quella proprietà. Capisci bene come un errore di disattenzione, proprio perché non segnalato da Xcode, può far accumulare errori che difficilmente noterai (se non in fase di testing o, peggio ancora, in fase di rilascio).

Quindi, se non vuoi provare l’ebrezza di uno sbarco in Normandia di commenti negativi, presta attenzione al codice che scrivi.

Ovviamente uno sbarco non è per niente paragonabile allo scoppia di una bomba atomica. Nel caso di utilizzo errato dell’objectForKey a scoppiare sarà proprio la tua applicazione. Dato che viene restituito nil o un AnyObject, se non correttamente controllato con un optional binding (if let) o con un guard, l’applicazione può crashare con conseguenze disastrose (in questo caso, però, è sicuramente più facile accorgersene).

Il mio consiglio, proprio perché il metodo objectForKey restituisce nil quando non c’è un valore, è quello di cercare di utilizzare sempre il setObject. In questo modo è facile controllare la presenza di valore e prendere le opportune misure. Questo è uno dei casi in cui l’opzionalità del linguaggio Swift ha un’importanza notevole nello sviluppo di un qualsiasi progetto.

Se molti dei concetti che ho citato ti risultano oscuri, ho scritto un corso gratuito sul linguaggio Swift per spiegarti le basi dello sviluppo iOS.

Ritorniamo all’app che stai realizzando. Salva tutti i dati utilizzando il setObject:

Adesso, nel metodo viewDidLoad, circoscrivi il recupero dei due oggetti all’interno di un controllo guard:

Dato che avevo già salvato il mio nome e la mia età, al successivo avvio, sulla console verranno stampati entrambi a dimostrazione dell’avvenuto recupero dei dati:

Schermata 2016-02-22 alle 13.26.19

Esempio BackgroundColor

Proviamo a creare una sorta di pagina Setting di una applicazione. In questa pagina ci sono tre bottoni che permettono di cambiare il colore di sfondo del ViewController. Una volta che l’utente clicca su uno dei tre bottoni, la preferenza deve essere salvata in memoria ed applicata all’avvio successivo.

Indubbiamente è un problema di memorizzazione delle preferenze dell’utente. Dato che si trattano di piccole quantità di memoria, invece del Core Data opterai per l’NSUserDefaults in Swift.

La pagina che devi creare è la seguente. Se dovessi trovare dei problemi, in fondo alla pagina trovi il download del progetto completo e commentato:

Schermata 2016-02-25 alle 01.42.08

Per il posizionamento dei bottoni ho utilizzato i vincoli di auto layout e le nuovissime stack view. Ogni bottone punta alla stessa IBAction. Dentro l’IBAction di gestione dei bottoni, la func changeColor_clicked, uno switch provvede ad eseguire un codice differente:

Grazie a questa IBAction ed allo switch, salverai nell’NSUserDefaults il colore da salvare in memoria. Come?

Ipotizziamo che l’utente selezioni il tasto “Rosso”. Questo attiverà l’IBAction changeColor_clicked che avvierà lo switch case “Rosso”. All’interno di questo case salverai in memoria il valore “rosso” ad una chiave chiamata, per esempio, “BackgroundColor”. All’avvio successivo l’applicazione leggerà il valore associato a tale chiave e cambierà il colore di sfondo della view.

Semplice no?

Procediamo per step. Prima dello switch, aggiungi un riferimento all’User Defaults standard. Successivamente, nel case “Rosso”, salva la stringa “red_color” alla chiave “BackgroundColor”. La stessa identica operazione eseguila per tutti gli altri case, cambiando per la chiave “BackgroundColor” il valore da salvare. Infine, dovresti avere il seguente codice:

Dato che stai salvando delle semplici stringhe e non dei colori, devi far in modo che queste corrispondano realmente a degli oggetti UIColor. Per poter facilmente associare una stringa ad un UIColor, potresti utilizzare un dizionario chiave-valore dove, ogni chiave è il nome del colore (uguale al nome inserito nell’NSUserDefaults) mentre il valore è un oggetto UIColor.

Aggiungi, alla classe ViewController, il seguente dizionario:

Dentro il metodo viewDidLoad, che è quello delegato al caricamento delle impostazioni del Controller prima che la View venga renderizzata nello schermo, puoi recuperare il valore salvato nell’NSUserDefaults, controllare che esista (ti ricordo che hai usato un setObject quindi se non viene eseguito ritorna nil il metodo ObjectForKey) ed associarlo al valore del dizionario. Infine, tale valore va passato alla view.backgroundColor del ViewController:

Ti basterà avviare l’applicazione, selezionare un colore e riavviare per vedere applicato il colore scelto nella precedente istanza dell’app:

NSUserDefaults in Swift esempio backgroundColor

Considerazioni

Uno degli altri possibili casi d’utilizzo, che ho trattato nel corso di sviluppo app iOS, è quello di utilizzare l’NSUserDefaults in Swift per visualizzare un tutorial d’anteprima dell’applicazione una sola volta durante l’arco della vita dell’applicazione (il classico tutorial che appare al primo avvio dell’app e poi non esce più).

Ad ogni modo ribadisco il concetto che l’NSUserDefaults è pensato per salvare in memoria piccole quantità di informazioni. Non mi va di fare una lista perché potrebbero essere davvero tanti i casi e le eccezioni. Quindi, se hai un dubbio e non sai se è corretto o meno applicare l’NSUserDefaults in Swift alla tua applicazione, lascia pure un commento. 

Se hai avuto problemi con il tuo progetto, puoi scaricare il mio una volta sbloccato il modulo:

[sociallocker][wpdm_package id=’42806′][/sociallocker]

Da questo tutorial non posso che consigliarti di andare ai seguenti:

Buona Programmazione!

Start typing and press Enter to search

SpeechSynthesizer in Swift