Core Data Panoramica e utilizzo Marco Nobile Matricola: 0000653725 Introduzione Core Data è un framework per la persistenza dei dati introdotto da Apple per iOS (e Mac OS X). Si compone di una serie di API che permettono di organizzare i dati tramite il modello relazionale, serializzato tramite codifica XML, binaria o secondo i protocolli SQLite. Tutti i dati possono, grazie a Core Data, essere manipolati tramite oggetti di più alto livello che rappresentano le Entità e le loro Relazioni. Core Data assolve molti dei “doveri” di un Modello in una architettura MVC (Model-View-Controller), si occupa di gestire le modifiche ai dati, la serializzazione su disco, e le queries, il tutto cercando di minimizzare l’utilizzo di memoria. Oltre a questo, Core Data presenta altre features interessanti: ● ● ● ● Validazione automatica dei valori delle proprietà Gestione sicura della migrazione dello schema Supporto completo e automatico per la codifica e l’osservazione chiave-valore Supporto automatico per memorizzare oggetti in repositories esterni Perchè utilizzare Core Data? Ricapitolando, è conveniente utilizzare questo framework nello sviluppo di applicazioni iOS per le seguenti ragioni: ● Diminuzione considerevole del numero di righe di codice da scrivere nello sviluppo del Model (dal 50% al 70%) ● Ha alla base del codice ottimizzato e viene periodicamente aggiornato. ● Possiede un Tool di modellazione grafica che permette la creazione di schemi anche complessi in modo semplice e intuitivo. ● Integrato con Interface Builder permette di creare UI dai modelli. Stack di Core Data Il Framework Core Data consiste di alcuni oggetti che, integrati, provvedono alle funzionalità di archiviazione. Questi vengono rappresentati in figura. 1 Managed Object Context Il codice dell’Applicazione iOS interagisce direttamente con questo oggetto (invece che con la base dati). Questo Context mantiene lo status degli oggetti e gestisce le loro relazioni. Tutte le interazioni col database sottostante sono mantenute temporaneamente in questo strato, fino a quando l’utente non specifica di salvare i cambiamenti permanentemente. Managed Objects Istanze della classe NSManagedObject, possono essere rappresentate come dei records in un tabella di un database relazionale. Managed Object Model Definisce il modello di un oggetto, analogamente ad un’entità. Infatti sono proprio le entità che definiscono il modello dei Managed Objects. Ogni oggetto avrà quindi un set di attributi, relazioni (one-to-one, one-to-many...), proprietà che possono essere accedute anche da altre entità(o Managed Objects), e una serie di richieste (query predefinite che possono essere utilizzate per estrapolare delle informazioni specifiche da un Object Model). 2 Persistent Store Coordinator E’ il responsabile della coordinazione degli accessi a multipli “Persistent Object Store” (definiti in seguito). Lo sviluppatore non interagirà mai direttamente con questo oggetto. Quando, raramente, è presente più di un Persistent Object Store, il Coordinator di occupa di presentarlo allo strato superiore come un unico store. Persistent Object Store E’ lo strato più basso della pila di Core Data, nel quale i dati vengono memorizzati. Vengono supportate le codifiche XML, binaria e SQLite(utilizzata di default dall’iOS SDK). Sviluppare App che utilizzano Core Data Di seguito verranno esplicati alcuni dei procedimenti chiave per integrare e utilizzare il framework Core Data in una applicazione iOS Creare un nuovo progetto includendo Core Data Per prima cosa, dobbiamo creare un progetto in XCode, dal quale poi partire a sviluppare l’applicazione desiderata. Una volta arrivati alla finestra di dettaglio del nuovo progetto, occorre abilitare la checkbox “Utilizza Core Data” Una volta creato il progetto, in aggiunta ai files soliti di un classico nuovo progetto, verrà creato anche un file speciale nominato: “NomeProgetto.xcdatamodeld”. E’ in quest’ultimo file che le descrizioni delle varie entità del nostro datamodello verranno memorizzate. Creare e descrivere un’Entità La descrizione di un’entità definisce il modello dei nostri dati. Per creare un’entità in un’Applicazione Core Data, bisogna selezionare il file .xcdatamodeld(descritto 3 sopra), XCode si incaricherà di aprire l’editor delle entità (per poi autogenerare del codice Swift). Si aprirà una finestra simile alla seguente: E’ da notare che è possibile scegliere come visualizzare delle entità create: in maniera testuale o graficamente come uno schema relazionale. Per cambiare modalità è sufficiente selezionarla nell’angolo in basso a destra. Per creare una nuova entità bisogna cliccare “Add Entity”. Una volta creata, basterà cliccare due volte sopra di essa (XCode la posizionerà nell’elenco sotto “Entities”) per modificarne il nome. Se si desidera aggiungere degli attributi alle entità basterà utilizzare il bottone “Add Attribute”, nel pannello di creazione attributo sarà necessario specificare il nome e il tipo dell’attributo( analogamente ad uno schema ER ). Generare una sottoclasse di NSManagedObject Come spiegato inizialmente, ad ogni datamodello (o entità), viene associata un’istanza della classe NSManagedObject, è quindi necessario estendere questa classe per modellare le diverse entità. Con XCode è possibile autogenerare il codice di queste sottoclassi, basta selezionare “Editor -> Create -> NSManagedObject Subclass...”. Nella finestra che compare in seguito bisognerà selezionare prima il datamodello, poi le entità di quest’ultimo da generare. Una volta terminata la procedura, viene creato dal sistema un file “NomeEntità.swift”, contenente la sottoclasse di NSManagedObject corrispondente all’entità. 4 Esempio di sottoclasse generata: import Foundation import CoreData class NomeEntità: NSManagedObject { @NSManaged var attributo1: Int @NSManaged var attributo2: String } Modifica del nome della classe riferita all’Entità Quando si utilizza Core Data con del codice Swift, è importante includere il nome dell’applicazione come parte del nome della classe, così da includerla nel suo namespace. Per effettuare questa modifica, basta aprire l’editor del file .xcdatamodeld, selezionare l’entità e aprire il Data Model Inspector nel pannello Utilities, basterà per esempio, cambiare il nome della Classe in questo modo: NomeEntità → NomeProgetto.NomeEntità Salvataggio dei dati su Persistent Store Quando, nella nostra applicazione, abbiamo bisogno di salvare un’entità in modo permanente, abbiamo bisogno di un’istanza di Managed Object Context, per ottenerla, basta seguire le linee di codice seguenti: let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext Di seguito, bisogna ottenere la descrizione dell’Entità desiderata, dunque creare una nuova istanza della sottoclasse corrispondente. Infine, bisogna istruire il Context di salvare i dati caricati, e intercettare un possibile errore per poterlo gestire. 5 let entityDescription = NSEntityDescription.entityForName("NomeEntità", inManagedObjectContext: managedObjectContext!) let entity = NomeEntità(entity: entityDescription!, insertIntoManagedObjectContext: managedObjectContext) entity.attributo1 = 10 entity.attributo2 = “Stringa” var error: NSError? managedObjectContext?.save(&error) if let err = error { //handle error } Caricamento di dati dal Persistent Store Così come salvare i dati, un’applicazione potrebbe ragionevolmente avere la necessità di cercare e estrapolare dal database delle informazioni riguardanti le entità salvate. Anche in questo caso bisogna ottenere la descrizione dell’Entità che si cerca, poi creare un “predicato” che specifichi, per esempio, che solo gli oggetti con un dato valore per un dato attributo vengano inseriti nel risultato della ricerca. Questo risultato poi è rappresentato da un array, dal quale, per ogni elemento, è possibile estrapolare gli attributi tramite il metodo “valueForKey”. Un esempio di implementazione si trova alla pagina seguente. 6 let entityDescription = NSEntityDescription.entityForName("NomeEntità", inManagedObjectContext: managedObjectContext!) let request = NSFetchRequest request.entity = entityDescription let pred = NSPredicate(format: "(attributo1 = %@)", 10) request.predicate = pred var error: NSError? var objects = managedObjectContext?.executeFetchRequest(request, error: &error) if let results = objects { if results.count > 0 { let match = results[0] as! NSManagedObject // handle here the single result using // “match.valueForKey(..)” } else { status.text = "No Match" } } 7