SQLite per iPhone: inserimento, join e ottimizzazione di Fabio Lecca
by Stefano Lo Duca
Eccoci all’ultima parte di questo importante tutorial su SQLite per iPhone creato sulla base dell’esperienza che Fabio Lecca ha avuto durante la creazione del suo software Onomastici per iPhone. In questo articolo vedremo come si inseriscono dei dati nuovi come Fabio ha implementato le funzioni di “accoppiamento” tra le tabelle e come ha cercato di ottimizzare le procedure per rendere il sistema più veloce e snello.
Inserimento dati.
Se i nostri contenuti fossero interamente statici, non avremmo necessità di effettuare dei comandi SQL INSERT nella nostra applicazione, in quanto potremmo preparare il nostro data base con uno script e caricarlo su iPhone già pronto. In Onomastici, quali sono le informazioni dinamiche che necessitano di essere inserite da programma? Si tratta del contenuto della rubrica del telefono, che viene letta all’avvio del programma e scaricata in una tabella chiamata “rubrica”. Ecco il codice che realizza tale funzionalità:[sourcecode language='c++']
if (sqlite3_open([path UTF8String], &database) == SQLITE_OK)
{
if (insert_statement == nil)
{
static char *sql = “INSERT INTO rubrica (nome,cognome,id) VALUES(?,?,?)”;
if (sqlite3_prepare_v2(database, sql, -1, &insert_statement, NULL) != SQLITE_OK)
{
NSAssert1(0, @”Error: failed to prepare statement with message ‘%s’.”, sqlite3_errmsg(database));
}
}
sqlite3_exec(database, “BEGIN;”,0,0,0);
for (NSInteger i = peopleArray.count – 1; i >= 0; –i)
{
person = [ peopleArray objectAtIndex:i];
NSString *firstName = person.name;
NSString *lastName = person.lastName;
// inserisco nel db …
sqlite3_bind_text(insert_statement, 1, [firstName UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_text(insert_statement, 2, [lastName UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_int(insert_statement, 3, i);
int success = sqlite3_step(insert_statement);
// Because we want to reuse the statement, we “reset” it instead of “finalizing” it.
sqlite3_reset(insert_statement);
if (success == SQLITE_ERROR)
{
NSAssert1(0, @”Error: failed to insert into the database with message ‘%s’.”, sqlite3_errmsg(database));
} else
{
// OK
}
[firstName release];
[lastName release];
} //for
sqlite3_exec(database, “END;”,0,0,0);
// “Finalize” the statement – releases the resources associated with the statement.
sqlite3_finalize(insert_statement);
sqlite3_close(database);
[/sourcecode]
Ecco le dovute osservazioni:
1) path questa volta deve contenere il percorso alla versione “scrivibile” del database nella cartella Documents
2) la funzione sqlite3_prepare_v2 predispone un “prepared statement” allo scopo di eseguire più volte la stessa query, in cui di volta in volta inseriremo i parametri mediante le funzioni sqlite3_bind_text e sqlite3_bind_int, a seconda del tipo di dato della colonna corrispondente
3) nel caso specifico di insert multiple, un modo per velocizzare l’inserimento è di aprire e chiudere una transazione mediante i comandi BEGIN e END, tutto ciò che viene manipolato all’interno non riceve una “commit” su disco, di conseguenza le operazioni sono molto più veloci:
[sourcecode language='c++']
sqlite3_exec(database, “BEGIN;”,0,0,0);
…
..comandi sql che modificano il DB
…
sqlite3_exec(database, “END;”,0,0,0);
[/sourcecode]
4) l’array peopleArray contiene i valori name e lastName estratti dalla rubrica del telefono mediante le opportune API
5) le funzioni:
[sourcecode language='c++']
sqlite3_bind_text(insert_statement, 1, [firstName UTF8String], -1, SQLITE_TRANSIENT);
[/sourcecode]
e
[sourcecode language='c++']
sqlite3_bind_int(insert_statement, 3, i);
[/sourcecode]
consentono ad ogni ciclo di riempire il prepared statement con l’opportuno valore puntuale, rispettivamente in posizione 1 e 3 e di tipo stringa (sempre in formato UTF8) e intero
6) sqlite3_reset viene usata per “svuotare” velocemente uno statement che dovrà essere riutilizzato a breve, senza perdere troppo tempo nella deallocazione e riallocazione
7) non fa male ripetere che sqlite3_finalize e sqlite3_close vanno sempre chiamate nella sequenza corretta, allo scopo di liberare le risorse utilizzate dalla libreria
non fa male ripetere che è sempre opportuno verificare tutti i codici di ritorno di ciascuna funzione, qualora il risultato sia diverso da SQLITE_OK occorre intraprendere l’azione correttiva adatta al caso specifico.
Join
E veniamo ora ad uno dei motivi per cui ho realmente deciso di usare SQLite.. per implementare la funzionalità di “accoppiamento” tra la tabella con i nomi della rubrica di iPhone e la tabella dei nomi dei Santi (calendariosanti), ho subito realizzato che tale operazione veniva realizzata “gratis” mediante la seguente query che utilizza il Join SQL:
select nome,cognome
from rubrica,calendariosanti
where calendariosanti.day=?
and calendariosanti.month=?
and rubrica.nome=calendariosanti.santo
order by nome,cognome
Il risultato di tale query, iterato per ogni giorno, fornisce gli elementi della tabella già pronti per la visualizzazione, senza dover faticare per implementare l’algoritmo usando le classi di Cocoa (con le relative problematiche di gestione della memoria)!
Grazie al tempo risparmiato, mi sono dedicato ad una simpatica feature, aggiungendo nella tabella ‘calendariosanti’ anche le righe con i nomi in varie lingue e soprattutto con i diminutivi (es. Francesco -> Ciccio), i quali hanno un’alta probabilità di essere utilizzati nella nostra amata rubrica.
Ottimizzazione
Durante la realizzazione del progetto Onomastici si è reso necessario effettuare un’ottimizzazione degli indici del data base, affinchè i tempi di risposta fossero minori possibile, per evitare inutili attese all’utente. Occorre sempre ricordare che gli utenti iPhone sono piuttosto “frettolosi” e odiano le attese!
Dalla semplice analisi delle query è possibile dedurre quali indici possano essere creati affinchè il recupero dei dati possa essere ottimizzato; ad esempio nel caso della query
SELECT santo1 FROM santi where day=? and month=?
la creazione del seguente indice:
CREATE INDEX santi3 on santi (day,month);
velocizza di circa 10 volte l’accesso al dato richiesto! In questo modo l’utente può sfogliare il calendario in modo che l’unica attesa sia quella strettamente necessaria per la realizzazione dell’effetto grafico CoreAnimation di una pagina che viene girata.
Conclusione
In questo articolo abbiamo solamente sfiorato le potenzialità di SQLite, ma spero di aver reso evidente come esso possa realmente semplificare la vita al programmatore. Ci sono molte altre caratteristiche avanzate, quali l’ottimizzazione mediante i piani di esecuzione delle query, la gestione transazionale e i trigger, che consentono una ottimizzazione spinta del database e la realizzazione di applicazioni anche molto complesse, per cui vi invitiamo ad approfondire l’argomento.
Mi auguro di vedere presto applicazioni su App Store in grado di manipolare sempre maggiori quantità di dati, in modo sempre più veloce ed affidabile!
Per voi Fabio Lecca, e un caloroso grazie da iPhone and Go… sir Lodux !




4 Comments. Subscribe to this post comments or trackback.
Febbraio 25th, 2009
ottimo … bel articolo
Luglio 11th, 2009
Ottimo.. proverò! COMPLIMENTI!
Febbraio 1st, 2010
Ciao,
intanto complimenti per il blog ed i tuoi tutorial. Ti volevo chiedere una informazione riguardo SQlite e la possibilità di attingere da un database remoto i dati di alcune tabelle. C’è un modo? Ti ringrazio
Agosto 21st, 2010
Ma se volessi poi testare l’applicazione sul mio iphone senza passare da Apple come faccio?
Nel senso esiste un sistema per firmare l’applicazione per il fw 4.0