Eccoci alla seconda puntata della nostra Guida all’uso di Core Data nelle nostre applicazioni iPhone e iPad.
Ne approfitto per augurare Buon Natale a tutti 🙂
In questo episodio:
- Core Data vs Database: le differenze
- Aggiungiamo al nostro programma la possibilità di modificare ed eliminare i record
- Sfruttiamo la procedura automatica per creare le nostre classi Model, che ereditano da NSManagedObject, per non dover ricorrere continuamente al KVC.
1. Core Data vs Database
Come abbiamo detto nella scorsa puntata, Core Data non è un database.
Quest’affermazione a prima vista potrebbe dare la stessa sensazione di dissonanza cognitiva di “Ceci n’est pas une pipe”, quindi è il caso di approfondire.
Il primo istinto, una volta data un’occhiata ai metodi disponibili, è quello di usarlo come un database, cioé fetch/manipola i dati/salvataggio, che poi è quello che facciamo in questo tutorial. Questo perché il nostro Model è molto semplice. Ma Core Data brilla soprattutto nella gestione di oggetti e rapporti fra oggetti anche molto complicati.
Il concetto fondamentale è che Core Data può avere SQLite come backend, ma non è semplicemente un wrapper.
Passando attraverso Core Data otteniamo delle funzionalità di gestione dei rapporti fra le Entità che, come vedremo più avanti in questo tutorial, sono autentici oggetti objective-C, sottoclassi di NSManagedObject: ciò significa che dobbiamo istanziarli prima di poter agire su di loro.
Core Data funzionerebbe anche senza backend, anche se in questo caso rinunceremmo alla persistenza dei dati: possiamo operare completamente in ram, se istanziamo tutti i record e li colleghiamo fra di loro possiamo arrivare a tutti i record senza usare ricerche e senza accedere al disco.
Manipolare oggetti in ram è più rapido che aggiornare un database, quando salviamo i dati del nostro contesto allora Core Data userà il database e quindi i tempi di salvataggio di Core Data si sommeranno a quelli del database.
Riassumiamo in una lista le caratteristiche dei database e di Core Data.
Database:
- può aggiornare un attributo senza caricare l’intero oggetto in memoria
- è lento nel creare nuovi record
- agisce su dati residenti sul disco rigido
Core Data:
- tiene aggiornate le connessioni fra gli oggetti automaticamente
- quando un oggetto viene cancellato è possibile cancellare automaticamente quelli che gli sono collegati (come vedremo in un prossimo tutorial)
- agisce su dati residenti in ram
- istanziare nuovi record è rapido
- le modifiche agli oggetti sono osservabili con il Key-Value Observing
Il compromesso:
in Core Data istanziare gli oggetti è rapido, perché sono in RAM piuttosto che sull’HD, ma per modificare anche un solo attributo o per cancellare un’oggetto è necessario caricarlo prima in RAM.
Quindi, se vogliamo sviluppare un’app, in cui modifichiamo molto spesso poche proprietà in più migliaia di record (esempio: lettore rss, letto, non letto) allora SQLite sarà più rapido di Core Data.
2. Creiamo le nostre classi Model
Torniamo all’app che abbiamo sviluppato nella scorsa puntata.
Finora abbiamo usato setValue:forKey: per modificare e ottenere i valori delle proprietà nelle istanze della nostra Entità.
Il Key-Value Coding ci permette di accedere ad ogni proprietà, di qualunque classe, per cui è disponibile un metodo setter e un metodo getter, che seguano questa naming convention:
- getter
- nomeVariabile (quindi non getNomeVariabile, che viene usato in un'altro caso)
- setter
- setNomeVariabile (sempre usando il Camel Case)
Quindi è una soluzione generica che funziona, ma che comporta un notevole spreco di caratteri, che scriviamo senza l’aiuto dell’autocompletamento di Xcode e soprattutto senza il type checking, per cui abbiamo una notevole gamma di errori possibili che non verranno segnalati al compile-time.
Per ovviare a questi inconvenienti possiamo creare una sottoclasse di NSManagedObject per la nostra Entità.
C’è un modo rapido di sottoclassare NSManagedObject avendo già definito gli attributi dell’Entità nell’editor grafico di Core Data: apriamo il nostro xcdatamodel nell’editor e selezioniamo l’Entità per cui vogliamo creare una classe, nel nostro caso Contatto.
A questo punto scegliamo dal menu File > New File…, sotto Cocoa Touch Class avremo accesso ad un nuovo template: Managed Object Class.


Andiamo avanti nello wizard e assicuriamoci che Add To Project: punti al nostro progetto e che nei targets sia selezionato CoreDataPart2, e nella schermata successiva che l’Entità sia selezionata (è possibile generare Classi per più Entità contemporaneamente, se il nostro file xcdatamodel contenesse più di un’Entità sarebbero tutte elencate qui), come anche Generate Accessors e Generate Obj-C 2.0 properties e poi confermiamo premendo Finish.


A questo punto i file .h e .m della nostra classe sono stati aggiunti al progetto, e nel file xcdatamodel la classe della nostra Entità, NSManagedObject è stata sostituita con la nuova classe, Contatto. E necessario salvare il file xcdatamodel per finalizzare il cambiamento.
Diamo un’occhiata all’interfaccia della classe Contatto.
#import
@interface Contatto : NSManagedObject
{
}
@property (nonatomic, retain) NSString * nick;
@property (nonatomic, retain) NSString * cognome;
@property (nonatomic, retain) NSString * nome;
@end
Vediamo che eredita da NSManagedObject e che anche se gli attributi dell’Entità non sono dichiarati come variabili d’istanza poi vengono comunque generate le proprietà.
Per capire questa particolarità dobbiamo vedere anche l’implementazione.
#import "Contatto.h"
@implementation Contatto
@dynamic nick;
@dynamic cognome;
@dynamic nome;
@end
Le proprietà non vengono sintetizzate usando la direttiva @syntesize, ma con la direttiva @dynamic, cioé promettiamo al compilatore che saranno presenti al runtime, generate e gestite da Core Data. Naturalmente se dovessero mancare a causa di errori di battitura o altro, l’app crasherebbe.
3. Aggiorniamo i metodi che utilizzano il KVC
Adesso che abbiamo la nostra classe Model dobbiamo rimaneggiare tutto il codice nell’applicazione dove abbiamo usato il KVC.
Per prima cosa importiamo l’header in CoreDataPart2AppDelegate.m;
#import "CoreDataPart2AppDelegate.h"
#import "Contatto.h"
e poi modifichiamo i metodi interessati: addContatto, cellForRowAtIndexPath.
addContatto:
//Otteniamo il puntatore al NSManagedContext
NSManagedObjectContext *context = [self managedObjectContext];
//Parte1: Creiamo un'istanza di NSManagedObject per l'Entità che ci interessa
//Parte2: Creiamo un'istanza della classe Model dell'Entità che ci interessa, usando comunque NSEntityDescription
Contatto *contatto = [NSEntityDescription
insertNewObjectForEntityForName:@"Contatto"
inManagedObjectContext:context];
//Parte1: Usando il Key-Value Coding inseriamo i dati presi dall'interfaccia nell'istanza dell'Entità appena creata
//Parte2: Inseriamo i dati usando le proprietà della classe Contatto
contatto.cognome = surnameField.text;
contatto.nome = nameField.text;
contatto.nick = nickField.text;
cellForRowAtIndexPath:
// Set up the cell...
//Parte1: istanziamo NSManagedObject perché gli oggetti dentro l'array sono di quel tipo
//Parte2: istanziamo invece Contatto
Contatto *contatto = [contattiList objectAtIndex:indexPath.row];
//Parte1: accediamo ai dati contenuti dal'oggetto utilizzando il Key-Value Coding
//Parte2: accediamo ai dati utilizzando le proprietà della classe Contatto
cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", contatto.nome, contatto.cognome];
cell.detailTextLabel.text = contatto.nick;
return cell;
Et voilà, proviamo la nostra app e vediamo che funziona come prima, ma usando la classe Model che abbiamo generato.
4. Aggiungiamo altre funzionalità: Modifica ed Elimina i record già inseriti
Per prima cosa aggiungiamo al nostro file .h due variabili d’istanza, due metodi e un protocollo.
Le variabili d’istanza sono currentRecord e addButton, un’intero e un’outlet.
La funzione di currentRecord è tenere traccia su che record agiranno addContatto e deleteContatto, se abbiamo selezionato un record nella tabella sarà uguale al suo indexPath.row, mentre quando non c’è record selezionato sarà uguale a -1.
La funzione di addButton è darci l’accesso al pulsante aggiungi, per cambiare il suo testo da “Aggiungi” in “Modifica” e viceversa.
Il protocollo da aggiungere a quelli supportati è UITableViewDelegate, di cui useremo didSelectRowAtIndexPath:.
I metodi sono deleteContatto e clearFields. Dovremo pulire i campi in due metodi diversi: addContatto e deleteContatto, quindi è meglio estrarre la funzionalità da addContatto ed inserirla in un metodo apposito: clearFields.
In deleteContatto, com’è facilmente deducibile dal nome, agiremo sul record selezionato in didSelectRowAtIndexPath: eliminandolo.
showContatti non verrà più invocato schiacciando il pulsante mostra, quindi sostituiamo IBAction con void.
#import
#import
@interface CoreDataPart2AppDelegate : NSObject {
UIWindow *window;
IBOutlet UITextField *nameField;
IBOutlet UITextField *surnameField;
IBOutlet UITextField *nickField;
IBOutlet UITableView *contattiTable;
IBOutlet UIButton *addButton;
NSInteger currentRecord;
NSArray *contattiList;
@private
NSManagedObjectContext *managedObjectContext_;
NSManagedObjectModel *managedObjectModel_;
NSPersistentStoreCoordinator *persistentStoreCoordinator_;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (NSString *)applicationDocumentsDirectory;
- (void) showContatti;
- (void) clearFields;
- (IBAction) addContatto;
- (IBAction)deleteContatto;
@end
Apriamo MainWindow.xib e colleghiamo l’outlet per addButton.

Adesso passiamo a CoreDataPart2AppDelegate.m e inseriamo i nuovi metodi, modificando quelli vecchi dove necessario.
Per prima cosa sostituiamo anche qui void ad IBAction per showContatti, poi aggiungiamo un’invocazione a showContatti in didFinishLaunchingWithOptions:, per mostrare eventuali record già presenti all’avvio dell’app e settiamo currentRecord = -1 come valore iniziale.
- (void) showContatti {
//Otteniamo il puntatore al NSManagedContext
NSManagedObjectContext *context = [self managedObjectContext];
//istanziamo la classe NSFetchRequest di cui abbiamo parlato in precedenza
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
//istanziamo l'Entità da passare alla Fetch Request
NSEntityDescription *entity = [NSEntityDescription
entityForName:@"Contatto" inManagedObjectContext:context];
//Settiamo la proprietà Entity della Fetch Request
[fetchRequest setEntity:entity];
//Eseguiamo la Fetch Request e salviamo il risultato in un array, per visualizzarlo nella tabella
NSError *error;
NSArray *fo = [context executeFetchRequest:fetchRequest error:&error];
contattiList = [fo retain];
[fetchRequest release];
[contattiTable reloadData];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[window makeKeyAndVisible];
//aggiorniamo la tabella
[self showContatti];
//settiamo il valore iniziale di currentRecord
currentRecord = -1;
return YES;
}
5. Inseriamo i nuovi metodi
Estraiamo le righe che si occupano di pulire i campi da addContatto e inseriamole nel nuovo metodo clearFields
- (void)clearFields {
//per pulire i campi
NSString *clear = [NSString stringWithFormat:@""];
[nameField setText:clear];
[surnameField setText:clear];
[nickField setText:clear];
}
E’ il momento di aggiungere i due metodi più importanti di questa fase: deleteContatto e didSelectRowAtIndexPath:
- (IBAction)deleteContatto {
//Otteniamo il puntatore al NSManagedContext
NSManagedObjectContext *context = [self managedObjectContext];
//usiamo currentRecord per agire sul record selezionato
[context deleteObject:[contattiList objectAtIndex:currentRecord]];
//Effettuiamo il salvataggio gestendo eventuali errori
NSError *error;
if (![context save:&error]) {
NSLog(@"Errore durante il salvataggio: %@", [error localizedDescription]);
}
//resettiamo surrentRecord
currentRecord = -1;
//puliamo i campi
[self clearFields];
//aggiorniamo la tabella
[self showContatti];
}
Infine modifichiamo addContatto per fargli gestire sia il caso della modifica sia quello dell’aggiunta, pulire i campi col nuovo metodo, aggiornare la tabella e resettare currentRecord:
- (IBAction) addContatto {
//apriamo alertView se i campi non sono riempiti
if (nameField.text.length == 0 || surnameField.text.length == 0 || nickField.text.length == 0) {
//Costruiamo l'alert che ci impedisce di inserire un record senza tutti i campi
NSString *titolo = @"Problema";
NSString *messaggio = @"Devi inserire tutti i campi!";
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:titolo
message:messaggio
delegate:self
cancelButtonTitle:@"OK, non lo farò più."
otherButtonTitles:nil];
;
;
} else {
//per far sparire la tastiera
if ([nameField isFirstResponder]) {
[nameField resignFirstResponder];
} else if ([surnameField isFirstResponder]) {
[surnameField resignFirstResponder];
} else if ([nickField isFirstResponder]){
[nickField resignFirstResponder];
}
//Otteniamo il puntatore al NSManagedContext
NSManagedObjectContext *context = [self managedObjectContext];
//dichiariamo contatto senza istanziarlo, lo faremo all'interno dell'if.
Contatto *contatto;
//se è selezionato un contatto agiremo su quello altrimenti ne aggiungeremo uno nuovo
if (currentRecord >= 0) {
contatto = [contattiList objectAtIndex:currentRecord];
} else {
//Parte1: Creiamo un'istanza di NSManagedObject per l'Entità che ci interessa
//Parte2: Creiamo un'istanza della classe Model dell'Entità che ci interessa, usando comunque NSEntityDescription
contatto = [NSEntityDescription
insertNewObjectForEntityForName:@"Contatto"
inManagedObjectContext:context];
}
//Parte1: Usando il Key-Value Coding inseriamo i dati presi dall'interfaccia nell'istanza dell'Entità appena creata
//Parte2: Inseriamo i dati usando le proprietà della classe Contatto
contatto.cognome = surnameField.text;
contatto.nome = nameField.text;
contatto.nick = nickField.text;
//Effettuiamo il salvataggio gestendo eventuali errori
NSError *error;
if (![context save:&error]) {
NSLog(@"Errore durante il salvataggio: %@", [error localizedDescription]);
}
//puliamo i campi nell'interfaccia
[self clearFields];
//aggiorniamo il testo del pulsante
[addButton setTitle:@"Aggiungi" forState:UIControlStateNormal];
}
//resettiamo currentRecord
currentRecord = -1;
//aggiorniamo la tabella
[self showContatti];
}
6. Tocchi finali
Per amor di evitare crash aggiungiamo un check in deleteContatto.
Controlliamo che currentRecord sia maggiore o uguale a zero prima di cancellare l’oggetto.
- (IBAction)deleteContatto {
if (currentRecord >= 0) {
//Otteniamo il puntatore al NSManagedContext
NSManagedObjectContext *context = [self managedObjectContext];
//usiamo currentRecord per agire sul record selezionato
[context deleteObject:[contattiList objectAtIndex:currentRecord]];
//Effettuiamo il salvataggio gestendo eventuali errori
NSError *error;
if (![context save:&error]) {
NSLog(@"Errore durante il salvataggio: %@", [error localizedDescription]);
}
//resettiamo surrentRecord
currentRecord = -1;
//puliamo i campi
[self clearFields];
//aggiorniamo la tabella
[self showContatti];
}
}
7. Abbiamo finito
Adesso è il momento di testare il programma, aggiungendo, modificando e cancellando record.
Il codice come al solito è sul mio github.
Riassunto
Abbiamo elencato le differenze fra Core Data e i database, abbiamo creato una classe Model con la procedura automatica, l’abbiamo utilizzata nell’app, abbiamo aggiunto la possibilità di modificare e cancellare i record.
Nella prossima puntata
Come inserire dati da un semplice file txt a Core Data.
9 Responses to “T#082 – Guida all’uso di Core Data (Parte 2)”
23 Dicembre 2010
Tweets that mention Guida all'uso di Core Data (Parte 2) iPhone e iPad | devAPP -- Topsy.com[…] This post was mentioned on Twitter by Rynox. Rynox said: RT @iPhone_devAPP: Guida all’uso di Core Data (Parte 2) http://www.devapp.it/wordpress/t082-guida-alluso-di-core-data-parte-2.html […]
23 Dicembre 2010
FraSalve ottimo tutorial 🙂
Vorrei chiedere una cosa: come faccio a rilevare il testo in un UILabel ad esempio abilitare un bottone solo se c’è un testo in una UILabel? Grazie
23 Dicembre 2010
doppioslashGrazie 🙂
Devi definire due IBOutlet nel file.h del tuo viewcontroller: una per l’UILabel e una per l’UIButton, collegarle alla label e al bottone in IB, sintetizzare le proprietà, e nel file .m, nel metodo dove vuoi effettuare il check, invocare il metodo di NSString isEqualToString sulla proprietà text della UILabel, con argomento il testo da rilevare:
if ([label2.text isEqualToString:@”Testo da rilevare”]) {
NSLog(@”E’ uguale”);
[button1 setEnabled:YES];
} else {
NSLog(@”E’ diverso”);
[button1 setEnabled:NO];
}
Nella comparazione fra oggetti isEqualToString:, (e gli altri metodi equivalenti, isEqualToArray:, etc) è da preferire a == (Cocoa Fundamentals Guide, Cocoa Objects, Object Comparison)
30 Dicembre 2010
SoleLunaRieccomi 🙂
Sto facendo alcuni test prendendo spunto da questo tutorial, come dicevo nei commenti della prima parte, sto realizzando un app con più view (view controller + xib) ho passato senza problemi il riferimento NSManagedContex alla view che utilizzera coredata.
Ora all’interno di questa view ho la necessita di aprirne una seconda, una subview,
su questa non ho necessita di accedere a coredata quindi non ho passato nessun riferimento a NSManagedContex, devo pero visualizzare dei valori, con riferimento al tutorial, dovrei visualizzare il valore di “contatto.nome”, ho passato il riferimento alla classe “contatto” da una view all’altra, tutto funziona correttamente, ed ho ottenuto ciò che volevo, ora pero per curiosità ho provato a modificare il valore di contatto.nome dalla subview e con stupore ho notato che la modifica è persistente, non avendo passato nessun riferimento a coredata, è forse merito della classe contatto?
grazie ancora e buon 2011 a tutti 🙂
29 Gennaio 2011
le0nCiao, ottimo articolo, aspetto i successivi 😉
mi permetto di aggiungere una piccola modifica, all’interno di showContatti, prima dell’istruzione “contattiList = [fo retain];” sarebbe meglio fare un release di contattiList 😉
22 Marzo 2011
PaoloCiao, sono nuovo della programmazione iphone. Mi sono impantanato quando dici di collegare la tableview. Non so come procedere nel settare una tableview “stand alone” correttamente … mi puoi segnalare se esiste un tutorial per colmare questo gap? Grazie
3 Luglio 2011
PipesifCiao,
Complimenti ottimo tutorial….. volevo però chiere.. a quanto la parte 3???
31 Agosto 2011
NDSComplimenti per gli articoli sul Core Data.
Aspetto anche io la parte 3.
🙂
5 Maggio 2012
frankp2138Ciao ho trovato la dimostrazione molto utile, ma devo dire per me che ho iniziato da poco ci sono delle poco chiare, ho eseguito le tue istruzioni ma l’applicazione va in crash, mi d a error nella parte di scrittura nel data base, e come se non lo trovasse oppure non ho eseguito dei passaggi che non erano indicati.
ti sarei molto grato se potresti aiutarmi.
grazie