Posts tagged: objective-c

Objective-C 2.0 – Direttive del compilatore

learn-objective-cSono alle prese con lo sviluppo di un’app per iPhone e siccome mi trovo a dover utilizzare, per forza di cose, le direttive messe a disposizione dal linguaggio, ho pensato che può essere utile avere una lista che le elenca e che ne spiega il significato. Ho trovato anche un post in rete molto interessante che in pratica fa la stessa cosa, e quindi ne approfitto per diffondere anche io nel panorama italiano.

Let’s go !

@class

Dichiara una classe senza dover utilizzare l’istruzione import con il relativo file header. Viene utilizzato per le class forward declaration.

@class DevMeClass;

@protocol @required @optional @end

Definisce l’inizio della dichiarazione di un protocol. Opzionalmente può dichiarare altri protocol ai quali è conforme come segue:

@protocol DevMeProtocol

 

  • @required: dichiara i metodi come metodi required.
  • @optional: dichiara i metodi come metodi optional. La classe che implementa il protocollo può decidere se implementare o no il metodo opzionale. Le classi che fanno uso del protocollo devono verificare che il metodo opzionale esista prima di utilizzarlo.
    [object respondsToSelector:@selector(optionalProtocolMethod)];
  • @end: dichiara la fine del protocollo.

@interface @public @package @protected @private @property @end

Definisce l’inizio di una classe o category.

Dichiarazione di una classe.

Le classi in Objective-C devono derivare direttamente o indirettamente dalla classe NSObject. La direttiva @interface per la dichiarazione di una classe può opzionalmente dichiarare che la classe è conforme ad altri protocolli.

@interface DevMeClass : DevMeSuperClass <DMProtocol, anotherDMProtocol> {
  // instance variables can be
@public
@package
@protected
@private
}
 
// property declarations
@property (atomic, readwrite, assign) id aProperty;
 
// public instance and/or class method declarations
@end

Dichiarazione di una category

La direttiva @interface non può aggiungere variabili d’istanza ma può opzionalmente dichiarare la conformità ad altri protocolli.

@interface DevMeClass (CategoryName) <DMProtocol, anotherDMProtocol>
 
// property declarations
@property (atomic, readwrite, assign) id dmProperty;
 
// method declarations
@end

Direttive

  • @public: dichiara la variabile d’istanza pubblica. Le varibili pubbliche possono essere lette o modificate con la notazione punto
    devme->bestBlog = true;
  • @package: dichiara la variabile d’istanza pubblica all’interno del framework che definisce la classe ma privata all’esterno.Funziona sui sistemi a 64 bit, su quelli a 32 bit si comporta esattamente come @public.
  • @protected: dichiara la variabile d’istanza protetta e quindi accessibile solo dalla classe che la definisce e dalle sue eventuali derivate.
  • @private: dichiara la variabile d’istanza privata e quindi accessibile solo dalla classe che la definisce.
  • @property: dichiara una proprietà la quale può essere accessibile con la notazione punto. Può essere eseguita dalle specialio parole chiave che ne definiscono l’esatto comportamento. Possono essere:
    • readwrite (default), readonly). Genera il getter e il setter (readwrite), solo il getter ((readonly).
    • assign (default), retain, copy. Si applica solo alle proprietà per cui è possibile effetuare un cast sul tipo id. assign, assegna il valore passato; retain, invia un release alla variabile d’istanza, invia retain al nuovo oggetto e assegna l’oggetto retained alla variabile d’istanza; copy invia un release alla varibile d’istanza, invia copy al nuovo oggetto e assegna l’oggetto copiato alla variabile d’istanza. Negli ultimi casi si è responsabili della deallocazione dell’oggetto.
    • atomic(default), non atomic. Le proprietà atomic sono thread-safe mentre le non-atomic possono essere accedute simultaneamente. Le non atomic sono pià veloci delle atomic e spesso usate nelle applicazione single thread.
    • weak (default), strong. Sono disponibili se la funzionalità ARC (automatic reference counting) è abilitata. La chiave strong è sinonimo di retain, mentre weak è sinonimo di assign.
  • @end: dichiara la fine della dichiarazione dell’interfaccia.

 

@throw @try @catch @finally

Vengono utilizzate per la gestione delle eccezioni.

@try {
    // code that might throw an exception &hellip; like this one:
    NSException *exception = 
        [NSException exceptionWithName:@"DevMeException"
                                reason:@"Test exception!"
                              userInfo:nil];
    @throw exception;
}
@catch (CustomException *ce) {
    // handling ...
}
@catch (NSException *ne) {
    // generic NSException handling ...
 
    // to simply re-throw the caught exception in a catch block:
    @throw;
}
@finally  {
    // after try or catch
}

@synchronized

Asscirua che il codice presente all’interno del blocco può essere disponibile solo ad un thread alla volta.

-(void) syncMethod:(id)object {
   @synchronized(object) {
      // code that works with locked object 
   }
}

@autoreleasepool

Nelle applicazioni in cui è abilitato ARC (Automatic Reference Counting) è necessario usare @autoreleasepool al posto di NSAutoreleasePool. Non bisognerebbe dichiarare una variabile all’interno di un blocco @autoreleasepool e continuare ad usarla dopo il blocco.

-(void) dmMethod {
    @autoreleasepool {
        // code that creates a large number of temporary objects
    }
}

@encode

Restituisce il carattere stringa della codifica di un tipo.

-(void) encMethod {
    char *enc1 = @encode(int);                 // enc1 = "i"
    char *enc2 = @encode(id);                  // enc2 = "@"
    char *enc3 = @encode(@selector(aMethod));  // enc3 = ":"
 
    // practical example:
    CGRect rect = CGRectMake(0, 0, 100, 100);
    NSValue *v = [NSValue value:&rect withObjCType:@encode(CGRect)];
}

@compatibility_alias

Consente di generare un alias per una classe. Il primo parametro è il nome dell’alias della classe, il quale può anche non esistere. Il secondo parametro è il nome della classe. a cui l’alias fa riferimento.

@compatibility_alias AliasClassName ExistingClassName

@string

Dichiara un oggetto NSString. Per tale oggetto non è necessario gestirne la memoria.

-(void) devmeMethod {
    NSString* str = @"Ciao questo &egrave; il blog di devme.";
    NSUInteger strLength = [@"Hello world!" length];
}

The end

Siamo giunti alla fine….per ora naturalmente. Sentitevi liberi di comunicare eventuali errori delle direttive. Nel frattempo, direttamente dal blog da cui ho tratto ispirazione, trovate la stessa lista in pdf ;) .

Stay tuned!

iPhone: NavigationController e Session Manager

iphone-guitarBentornati su devme! In questo articolo volevo parlare di una strategia che ho impiegato nel progetto di lavoro di cui mi sto occupando, che tratta dello sviluppo di un’applicazione per iPhone (e altre piattaforme), e del quale spero se ne sentirà parlare. Non presenterò una discussione sul funzionamento dell’applicazione, ma di un aspetto particolare che ho utilizzato per risolvere un problema. Volevo proporre la mia soluzione (sicuramente ne esisteranno delle altre più ottimizzate) e discuterne con chi ne fosse interessato.
L’analogia che si può riscontrare è quella web. Si pensi ad esempio alla navigazione tra pagine web, ciascuna delle quali visualizza il suo contenuto sulla base delle scelte effettuate nelle pagine precedenti. Nel contesto di un’applicazione molto semplice si potrebbe far uso delle Sessioni messe a disposizione dal linguaggio utilizzato per lo sviluppo (es. php). Il valore selezionato nella prima pagina viene memorizzato in Sessione e recuperato nella pagina finale per l’estrazione dei dati di dettaglio, di un’ipotetica scheda prodotto. Nella mia applicazione ho utilizzato la stessa tecnica, in questo contesto mi è sembrata la più semplice…ovviamente ditemi se esiste di meglio :)
L’applicazione è molto varia, utilizza molti componenti messi a disposizione dall’ SDK iOS, e cioè TabbedBar, NavigationController, gestione degli eventi, TableViewController, ricerche su tabelle e altro. Nello specifico, tratteremo della navigazione attraverso delle view utilizzando il NavigationController.
Ma passiamo al codice: 

@@ SessionManager.h @@
 
#import <Foundation/Foundation.h>
#import "SessionObject.h"
 
@interface SessionManager : NSObject {
 
	NSMutableDictionary *session;
}
 
@property (nonatomic, retain) NSMutableDictionary *session;
 
/** 
 * Get singleton instance of Session Manager.
 */
+(SessionManager*) getInstance;
 
/**
 * Init session object.
 */
-(void) initSession;
 
/**
 * Put object into session.
 */
-(void) put: (NSString*)key object:(NSObject*)object;
 
/**
 * Retrieve object from session if exists.
 */
-(NSObject*) get: (NSString*)key;
@end
 
#import "SessionManager.h"
 
@@ SessionObject.m @@
 
@implementation SessionManager
 
@synthesize session;
 
static SessionManager *instance = nil;
 
+(SessionManager*) getInstance {
    @synchronized([SessionManager class]) {
        if (!instance)
            [[self alloc] init];
	return instance;
    }
    return nil;
}
 
+(id) alloc {
    @synchronized([SessionManager class]) {
        NSAssert(instance==nil, @"Attempted to allocate a second instance of SessionManager singleton");
	instance = [super alloc];
	return instance;
    }
    return nil;
}
 
-(id) init {
    self = [super init];
    if (self != nil) {
	[self initSession];
    }
    return self;
}
 
-(id) retain {
    return self;
}
 
-(void) initSession {
    session = [[NSMutableDictionary alloc] init];
}
 
-(NSObject*) get:(NSString *)key {
    NSObject *obj = [session objectForKey:key];
    return obj;
}
 
-(void) put:(NSString *)key object:(NSObject *)object {
    [session setObject:object forKey:key];
}
 
@end

La prima parte del codice fa riferimento alla definizione del file header per la classe SessionManager, ovvero la classe che memorizzerà le informazioni in sessione. La classe è un singleton, esattamente indentica a quella vista in questo articolo. Possiede un’unica variabile membro, un array associativo NSMutableDIctionary che consente la memorizzazione di oggetti in sessione, allo stesso modo in cui avviene ad esempio in php ($_SESSION["key"] = $value). Nel nostro caso il valore in sessione viene memorizzato attraverso l’invocazione del metodo put, il quale prende in input 2 parametri, la chiave e il valore e lo memorizza in Sessione. Il metodo simmetrico, consente di estrarre il valore dalla sessione attraverso la specifica della chiave. Possiede i metodi di inizializzazione che consentono di allocare e inizializzare l’array associativo.

Consideriamo ora di aver creato una classe NavigationController per permettere la navigazione tra le diverse view, e che la prima delle nostre view sia di tipo UITableViewController. Senza scendere nei particolari vediamo solo i metodi più signitficativi della nostra classe:

#import "DevmeTableViewController1.h"
 
@implementation DevmeTableViewController1
 
@synthesize devmeTableViewController2;
 
- (void)viewDidLoad {
    [super viewDidLoad];
    //&nbsp;Populate datasource table with Car object
    [self loadTableContent];
    UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@"Tabella1" style:UIBarButtonItemStylePlain target:nil action:nil];
    self.navigationItem.backBarButtonItem = backButton;
    [backButton release];
    self.navigationItem.title = @"Dati tabella 1";	
}
 
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
 
    // Configure the cell...
    Car *car = [datasource objectAtIndex:indexPath.row];
    // Display cars brand
    cell.textLabel.text = car.brand;
    return cell;
}
 
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    Car *car = [datasource objectAtIndex:indexPath.row];
    session = [SessionManager getInstance];
    [session put:@"BRAND" object:car.brand];
 
    DevmeTableViewController2 *tvController = [DevmeTableViewController2 alloc];
    self.devmeTableViewController2 = tvController;
    [tvController release];	
    [self.navigationController pushViewController:self.devmeTableViewController2 animated:YES];
}
 
..... other methods defined here.....
.....
.....
@end

La classe DevmeTableViewController1 visualizza un elenco di marche automobilistiche. La tabella viene popolata dal metodo loadTableContent omesso per semplicità, ma molto semplice da comprendere. Il metodo restituisce l’elenco delle marche automobilistiche estratte da un database popolando l’array datasource. Il metodo interessante per i nostri scopi è didSelectRowAtIndexPath il quale si occupa della navigazione per così dire, da una view ad un’altra. In particolare osserviamo all’interno del metodo che viene selezionato il valore associato alla cella della tabella che è stata selezionata (tapped), estraendolo dal datasource. Lo stesso valore viene salvato in sessione attraverso l’invocazione del metodo put sull’oggetto singleton SessionManager con chiave BRAND.
La view di arrivo è anch’essa una UITableViewController, la quale estrae il valore dalla sessione e popola opportunamente la tabella:

#import "DevmeTableViewController2.h"
 
@implementation DevmeTableViewController2
 
@synthesize devmeTableViewController3;
 
.... other methods here
 
- (void)viewDidLoad {
    [super viewDidLoad];
    session = [SessionManager getInstance];
    NSString *brand = (NSString*) [session get:@"BRAND"];
    self.navigationItem.title = brand ;
    [self loadTableContent:brand];	
}
 
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
   session = [SessionManager getInstance];
   [session put:@"MODEL" object:[datasource objectAtIndex:indexPath.row]];
 
   DevmeTableViewController3 *vdController = [DevmeTableViewController3 alloc];
   self.devmeTableViewController3 = vdController;
   [vdController release];
   [self.navigationController pushViewController:self.devmeTableViewController3 animated:YES];	
}
 
.... other methods here
@end

Anche in questo caso abbiamo solo i metodi più significativi, viewDidLoad il quale estrae il valore dalla sessione e popola la tabella invocando loadTableContent passando come parametro il valore selezionato in precedenza. Mentre il metodo didSelectRowAtIndexPath preleva il valore selezionato e lo inserisce in Sessione, rendendolo disponibile così alla view successive, infine sposta la navigazione verso l’ultima delle View. Per semplicità non vedremo il codice dell’ultima view, il quale è una ripetizione di quanto già visto. Possiamo considerare l’ultima view una semplice UIViewController, la quale preleva i valori dalla Sessione e invoca un metodo di estrazione dei dati dal database fornendo in input i 2 valori.
E’ tutto. Come vedete delle semplici analogie web possono essere utilizzate nel contesto di un’applicazione per iPhone, rendendo a mio avviso, facile la gestione. Questa applicazione verrà portata anche su dispositivi mobili che montano Android, nei prossimi articoli vedremo qualche esempio di quell’applicazione.
Alla prossima. Stay tuned !

 

iPhone: SQLite e singleton

iphone-guitarQuesto è il mio articolo sull’iphone e per l’occasione volevo parlare dell’utilizzo di SQLite all’interno della gestione del pattern singleton. SQLite è un insieme di librerie che implementano un motore di database self-contained che per la sua leggerezza e compatibiltià con gli standard SQL risulta essere il database più diffuso. Siccome è molto leggero richiede poche risorse, quindi ha ha senso che venga utilizzato all’inteno di dispositivi embeeded quali ad esempio l’iphone. l’iPhone implemente le librerie di SQLite fornendo così il supporto al database.

Passiamo subito ad un esempio pratico dando per scontato un conoscenza di base del linguaggio objective-c.
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//
//  DBHandle.h
//
//  Created by mulp on 11/05/10.
//
 
#import <Foundation/Foundation.h>
#import <sqlite3.h>
 
/* Define database name */
extern NSString * const databaseName;
 
@interface DBHandle : NSObject {
    /* Database variables */
    NSString *databasePath;
 
    /* Database handle */
    sqlite3 *dbconn;
}
 
@property (nonatomic, retain) NSString *databasePath;
 
/** 
 * Get singleton instance of this class, or rather
 * the only database handle.
 */
+(DBHandle*) getInstance;
 
/**
 * Connect to database.
 */
-(int) connectDB: (NSString*) path;
 
/**
 * Disconnect to database.
 */
-(void) disconnectDB;
 
/**
 * Check if the SQL database has already been saved to the users phone, 
 * if not then copy it over.
 */
-(void) checkAndCreateDatabase;
 
 
-(NSMutableArray*) getList;
@end

Analizziamo il file header della classe che sara DBHandle. La definizione dello header è molto semplice consiste in una costante che indica il nome del database, i classici metodi di connessione e disconnessione dal db. Un metodo (statico) per indenderci quello che ha il segno + davanti, che può essere invocato senza aver bisogno di instanziare la classe, che permette di ottenere l’istanza singleton della classe. Ed infine un metodo che verifica la presenza del database e all’occorrenza lo crea ed un metodo che restituisce la lista di elementi presenti all’interno di una tabella di esempio. Esistono altri metodi che non compaiono all’interno della definizione del file header in quanto privati alla classe DBHandle che andiamo a vedere.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//
//  DBHandle.m
//
//  Created by mulp on 11/05/10.
//
 
#import "DBHandle.h"
 
// Usage: [[DBHandle getInstance] method_name];
 
@implementation DBHandle
 
@synthesize databasePath;
 
static DBHandle *instance = nil;
NSString * const databaseName = @"devme.sql";
 
+(DBHandle*) getInstance {
    @synchronized([DBHandle class]) {
	if (!instance)
	    [[self alloc] init];
	    return instance;
	}
    return nil;
}
 
+(id) alloc {
    @synchronized([DBHandle class]) {
	NSAssert(instance==nil, @"Attempted to allocate a second instance of a singleton");
	instance = [super alloc];
	return instance;
    }
    return nil;
}
 
-(id) init {
    self = [super init];
    if (self != nil) {
	[self checkAndCreateDatabase];
    }
    return self;
}
 
-(id) retain {
    return self;
}
 
-(int) connectDB:(NSString *)path {
    // Open the database.
    int status = sqlite3_open([path UTF8String], &dbconn);
    if (status != SQLITE_OK) {
        // Even though the open failed, call close to properly clean up resources.
        sqlite3_close(dbconn);
        NSAssert1(0, @"Failed to open database with message '%s'.", sqlite3_errmsg(dbconn));
        // Additional error handling, as appropriate...
    }
    return status;
}
 
-(void) disconnectDB {
    sqlite3_close(dbconn);
}
 
-(void) checkAndCreateDatabase{
 
    // Get the path to the documents directory and append the databaseName
    NSArray *homePaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *homeDir = [homePaths objectAtIndex:0];
    self.databasePath = [homeDir stringByAppendingPathComponent:databaseName];
 
    BOOL success;
 
    // Create a FileManager object, we will use this to check the status
    // of the database and to copy it over if required
    NSFileManager *fileManager = [NSFileManager defaultManager];
 
    // Check if the database has already been created in the users filesystem
    success = [fileManager fileExistsAtPath:databasePath];
 
    // If the database already exists then return without doing anything
    if(success) return;
 
    // If not then proceed to copy the database from the application to the users filesystem
 
    // Get the path to the database in the application package
    NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:databaseName];
 
    // Copy the database from the package to the users filesystem
    [fileManager copyItemAtPath:databasePathFromApp toPath:databasePath error:nil];	
    [fileManager release];
}
 
-(NSMutableArray*) getItemsList {
    NSMutableArray * container = [[NSMutableArray alloc] init];
 
    NSLog(@"Path is: %@", databasePath);
    // Open the database from the users filessytem
    if(sqlite3_open([self.databasePath UTF8String], &dbconn) == SQLITE_OK) {
	// Setup the SQL Statement and compile it for faster access
	const char *sqlStatement = "SELECT strftime('%s', publish_days) / 86400, title FROM devme ORDER BY publish_days DESC";
	sqlite3_stmt *compiledStatement;
 
	if(sqlite3_prepare_v2(dbconn, sqlStatement, -1, &compiledStatement, NULL) != SQLITE_OK) {
	    NSLog(@"Error: failed to prepare stmt with message %s", sqlite3_errmsg(dbconn));
	}
 
	DevMe *devme;
	while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
	    devme = [[Devme alloc] init];
	    // Read the data from the result row
	    devme.pdate = [NSNumber numberWithInt:sqlite3_column_int(compiledStatement, 0)];
	    devme.title = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
	    [container addObject:devme];
	}
 
	// Release the compiled statement from memory
	sqlite3_finalize(compiledStatement);
	[devme release];
    }
    return container;
}
 
@end

Come vedete anche la definizione della classe è molto semplice. Osserviamo subito la definizione di una variabile static che rappresenta l’istanza della classe singleton. Per ottenerla basta richiamare il metodo getInstance il quale alloca materialmente la memoria per l’oggetto, e lo inizializza invocanto il metodo init. All’interno dell’inizializzazione viene invocato il metodo checkAndCreateDatabase. SQLite memorizza i dati all’interno di un file il quale viene creato al primo accesso al database. Il metodo verifica la presenza del database e se non lo trova crea materialmente il file e lo rende disponibile all’uso. La catena di metodi consente quindi di allocare e inizializzare l’istanza della classe DBHandle e restituirla al chiamante. Sull’istanza della classe sarà poi possibile invocare i metodi di connessione e disconnessione al database e quindi l’invocazione del metodo che restituisce la lista di elementi presenti all’interno di una tabella di esempio del database. Una possibile invocazione potrebbe essere la seguente:

dbhandle = [DBHandle getInstance];
[dbhandle connectDB:@"devme.sql"];
NSMutableArray *datasource = [dbhandle getItemsList];
[dbhandle disconnectDB];

Il metodo getItemsList esegue una query sul database e preleva i 2 attributi della tabella nel nostro esempio devme. I 2 attributi estratti vengono assegnati alle proprietà dell’oggetto Devme il quale viene poi aggiunto all’interno dell’array container. Quindi in definitiva il container contiene una lista di oggetti del tipo Devme.

In questo modo utilizzando un singleton si ha la possibilità da qualunque punto della nostra applicazione di accedere all’unica istanza della classe manager del database evitando così eventuali errori di gestione della connessione e ottimizzando l’accesso al database stesso. Nei prossimi articoli vedremo esempi di utilizzo di oggetti grafici di base messi a disposizione dall’SDK dell’iphone.

Stay tuned !

 

You need to log in to vote

The blog owner requires users to be logged in to be able to vote for this post.

Alternatively, if you do not have an account yet you can create one here.

Powered by Vote It Up

WordPress Themes