Dichiarare un block type per un completion handerl

In relazione all’articolo Creare un completion handler, si può precisare quanto illustrato qui:

La dichiarazione del block type è un modo per rendere più facilmente modificabile la signature del metodo, poiché nel primo caso basta cambiare la definizione del blocco, senza toccare il metodo.

1
2
3
4
typedef BOOL (^SomeBlockType)(id object, NSUInteger idx, BOOL *stop);
 
- (void)collectionToCheck:(SomeBlockType)checkerBlock;
- (void)singleItemToCheck:(SomeBlockType)checkerBlock;
typedef BOOL (^SomeBlockType)(id object, NSUInteger idx, BOOL *stop);

- (void)collectionToCheck:(SomeBlockType)checkerBlock;
- (void)singleItemToCheck:(SomeBlockType)checkerBlock;

versus:

1
2
- (void)collectionToCheck:(BOOL(^)(id object, NSUInteger idx, BOOL *stop)) checkerBlock;
- (void)singleItemToCheck:(BOOL(^)(id object, NSUInteger idx, BOOL *stop)) checkerBlock;
- (void)collectionToCheck:(BOOL(^)(id object, NSUInteger idx, BOOL *stop)) checkerBlock;
- (void)singleItemToCheck:(BOOL(^)(id object, NSUInteger idx, BOOL *stop)) checkerBlock;

Creare una MKAnnotation

MKAnnotation non è una classe, ma è un protocollo a cui deve aderire la classe che andremo a realizzare per visualizzare l’annotazione sulla mappa.

Per questo dobbiamo aggiungere una nuova coppia di file al progetto creando la classe (ad esempio) MyAnnotation. Il file di intestazione e implementazione saranno qualcosa di simile a quanto segue:

1
2
3
4
5
6
7
@interface MyAnnotation : NSObject<MKAnnotation> {
    CLLocationCoordinate2D coordinate;
}
 
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
 
- (id) initWithCoordinate:(CLLocationCoordinate2D)coord;
@interface MyAnnotation : NSObject<MKAnnotation> {
    CLLocationCoordinate2D coordinate;
}

@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;

- (id) initWithCoordinate:(CLLocationCoordinate2D)coord;

e

1
2
3
4
5
- (id) initWithCoordinate:(CLLocationCoordinate2D)coord
{
    coordinate = coord;
    return self;
}
- (id) initWithCoordinate:(CLLocationCoordinate2D)coord
{
    coordinate = coord;
    return self;
}

Poi sarà sufficiente aggiungere l’annotazione alla mappa:

1
2
3
MyAnnotation * annotation = [[MyAnnotation alloc] initWithCoordinate:coordinate];
 
[self.mapView addAnnotation:annotation];
MyAnnotation * annotation = [[MyAnnotation alloc] initWithCoordinate:coordinate];

[self.mapView addAnnotation:annotation];

Se si vogliono anche titolo e sottotitolo nel callout dell’annotazione, sarà sufficiente aggiungere le proprietà title e subtitle.

Sommare due NSNumber

Può sembrare una domanda banale, ma eseguire operazioni su un NSNumber richiede un minimo di attenzione dal momento che stiamo parlandoci oggetti e non di variabili scalari.

Per sommare due NSNumber, infatti, bisogna prima trasformarli in scalari (ad esempio float) e poi usare il risultato per creare un nuovo NSNumber object. Ecco l’esempio:

1
NSNumber *sum = [NSNumber numberWithFloat:([one floatValue] + [two floatValue])];
NSNumber *sum = [NSNumber numberWithFloat:([one floatValue] + [two floatValue])];

Uso del blocco @try

In tutte quelle situazioni in cui possono verificarsi potenziali errori, il linguaggio obiettive-c ci mette a disposizione una struttura di controllo che permette di “provare” il codice. Si tratta del blocco @try, seguito da @catch (eseguito solo se viene sollevata una eccezione) e da @finally (eseguito in ogni caso).

1
2
3
4
5
6
7
8
9
10
11
12
NSString* test = [NSString stringWithString:@"ss"];
 
 @try {
    [test characterAtIndex:6];
 
 }
 @catch (NSException * e) {
    NSLog(@"Exception: %@", e);
 }
 @finally {
    NSLog(@"finally");
 }
NSString* test = [NSString stringWithString:@"ss"];

 @try {
    [test characterAtIndex:6];

 }
 @catch (NSException * e) {
    NSLog(@"Exception: %@", e);
 }
 @finally {
    NSLog(@"finally");
 }

Bisogna ricordarsi di disattivare eventuali breakpoint settari su exception perché il programma si bloccherebbe (in pausa) anche se questo codice è pensato proprio per evitare interruzioni.

Sostituire un oggetto in una particolare posizione di un array

Ci sono molti casi in cui diventa necessario aggiungere elementi in NSMutableArray senza però usare il metodo addObject:, il quale, ovviamente, aggiunge l’oggetto in coda all’array, in ultima posizione. Si può usare quindi il metodo insertObject:atIndex. Ma ancora più utile è sapere che se, per esempio, si volesse sostituire un UIBarButtonItem in una ToolBar, non è necessario andare andando a cercare nell’array [items mutableCopy] l’UIBarButtonItem da eliminare, rimuoverlo con removeObjectAtIndex: e poi inserire quello nuovo con insertObject:atIndex. Si può infatti direttamente usare replaceObjectAtIndex:.

Sapere se la connessione è andata in timout

Usando NSURLConnection può capitare, per i più svariati motivi, che la connessione e il download dei dati non avvengano entro un certo tempo limite, detto timeout. Superato questo limite la connessione decade.

Per sapere se la connessione si è interrotta proprio per il raggiungimento del timeout si possono sfruttare le informazioni contenute nell’oggetto NSError usato per far partire la connessione, in questo modo:

1
2
3
4
5
6
NSError *error = nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
 
if (error.code == NSURLErrorTimedOut) {
 
}
NSError *error = nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

if (error.code == NSURLErrorTimedOut) {

}

nel creare la NSURLRequest, si può impostare questo valore di timeout, in secondi:

1
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30];

Creare un completion handler

Comodissimi sono i blocchi di codice eseguiti quando un certo metodo si conclude, in particolare animazioni e metodi in background, gestiti con GCD (Grand Central Dispatch) o direttamente da classi Apple, come NSURLConnection. Può essere quindi utile saper creare un completion handler, ovvero un blocco di codice che viene eseguito solo quando un metodo effettivamente si conclude, espandendo quindi le funzionalità in maniera importante.

Innanzitutto si dichiara il blocco di ritorno del completion handler, avendo cura di inserire gli eventuali oggetti e variabili da ritornare: ad esempio un codice che effettui il download di un  pacchetto di dati potrebbe restituire un oggetto NSData e/o un BOOL success:

1
typedef void(^ReturnBlock)(NSData *data, BOOL success);
typedef void(^ReturnBlock)(NSData *data, BOOL success);

Il completion handler può essere quindi una variabile di istanza di tipo ReturnBlock dichiarata nello stesso file d’interfaccia in cui si è definito il ReturnBlock:

1
2
3
4
@interface MyClass : NSObject
{
    ReturnBlock _completionHandler;
}
@interface MyClass : NSObject
{
    ReturnBlock _completionHandler;
}

Il metodo che fa uso di questo completio handler sarà qualcosa del genere

1
- (void)doSomethingWithCompletionHandler:(ReturnBlock)aCompletionHandler;
- (void)doSomethingWithCompletionHandler:(ReturnBlock)aCompletionHandler;

Nella cui implementazione, se ad esempio si sta usando la classe NSURLConnection per gestire il download, si scriverà nel metodo delegato:

1
2
3
4
5
6
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
 
if( _completionHandler )
    _completionHandler(data, YES);
 
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

if( _completionHandler )
    _completionHandler(data, YES);

}

Quindi, ovunque allochiamo e inizializiamo una istanza di MyClass, possiamo anche usare il seguente modo il metodo:

1
2
3
[myClass doSomethingWithCompletionHandler:^(NSData *data, BOOL success)^{
 
}];
[myClass doSomethingWithCompletionHandler:^(NSData *data, BOOL success)^{

}];

il blocco di codice verrà eseguito solo al completamento di qualunque cosa doSomething faccia, passando l’oggetto data e la variabile booleana success.

N.B.: con ARC non c’è bisogno di copiare il completion handler nel metodo doSomething né di rilasciarlo in dealloc.

Come non bloccare mai l’interfaccia con le NSOperation

Il problema del caricamento da internet o dell’elaborazione di un grande numero di dati è che l’interfaccia dell’applicazione si blocca facilmente e non risponde ai comandi dell’utente. Questo è dovuto al fatto che i metodi incriminati vengono tutti eseguiti nel thread principale, ovvero quello in cui gira anche il loop dell’interfaccia.

Possibili soluzioni sono quei metodi del tipo performSelectorInBackground:withObject: che però, all’atto pratico non garantiscono una fluidità dell’interfaccia in casi come il caricamento di una immagine da internet tramite il metodo dataWithContentsOfURL:.

Per risolvere definitivamente questo problema è necessario non solo che un metodo potenzialmente bloccante giri su un thread diverso dal MainThread, ma che lo faccia senza fretta e quindi senza pericolo che il sistema venga lo stesso bloccato.

Per questo Apple ha messo a disposizione la classe NSOperationQueue che permette di aggiungere ad una lista d’attesa una serie di metodi da eseguire in background. Appena sarà possibile, l’app le eseguirà una dietro l’altra. Il tempi di attesa possono risultare leggermente superiori, ma ne gioverà l’usabilità dell’interfaccia, con utenti più contenti.

Ecco un esempio del modo più semplice di usare questa classe:

1
2
3
4
5
self.queue = [NSOperationQueue new];
 
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadData) object:nil];
 
[self.queue addOperation:operation];
self.queue = [NSOperationQueue new];

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadData) object:nil];

[self.queue addOperation:operation];

Nell’esempio queue è una property dichiarata nel .h e quindi richiamabile in ogni parte del codice di implementazione.

È importante però ricordarsi che le modifiche all’interfaccia devono girare nel MainThread e che quindi, all’interno di un metodo inserito in una NSOperation bisogna usare il metodo performSelectorOnMainThread:withObject:waitUntilDone: ogni volta che serve di modificare l’interfaccia, impostando waitUntilDone a YES.

Creare un popover: una roadmap

Uno degli elementi d’interfaccia più utilizzati su iPad è il popover, elemento che sostituisce i menu a tendina e che discende spesso da un UIBarButtonItem di una toolbar (anche se è possibile collegarlo a qualsiasi elemento dell’interfaccia tramite il metodo presentPopoverFromRect:).

Per utilizzare al meglio un popover è necessario progettarlo con le seguenti caratteristiche

  1. UIPopoverController dev’essere una proprietà sintetizzata nel viewController, di modo da dismetterlo in qualunque momento senza problemi.
  2. Al popover dev’essere associato un protocollo (vedi qui come fare), per fare in modo che il viewController che ha creato il popover (viewController che sarà il suo delegato) riceva informazioni quando viene toccato un pulsante o simile del popover.
  3. Se il popover contiene una tabella, ma anche in altre situazioni, il viewController relativo potrà autodefinire la propria dimensione, di modo che il popover si regoli di conseguenza. Per questo si usa la proprietà del viewController self.contentSizeForViewInPopover, che è un CGSize.
  4. Al tocco del UIBarButtonItem si associa alla proprietà di tipo UIPopoverController un popover che contiene la vista da mostrare:
    1
    2
    
    myPopoverViewController = [[PopoverViewController alloc] initWithNibName:@"PopoverViewController" bundle:nil];
    popoverController = [[UIPopoverController alloc] initWithContentViewController:PopoverViewController];
    myPopoverViewController = [[PopoverViewController alloc] initWithNibName:@"PopoverViewController" bundle:nil];
    popoverController = [[UIPopoverController alloc] initWithContentViewController:PopoverViewController];
  5. Al momento della presentazione del popover si può anche scegliere la direzione della freccia verso cui deve aprirsi:
    1
    
    [popoverController presentPopoverFromBarButtonItem:popoverButton permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
    [popoverController presentPopoverFromBarButtonItem:popoverButton permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
  6.  Per dismettere il popover si può chiamare semplicemente il seguente metodo
    [popoverController dismissPopoverAnimated:YES];

    ma sfruttando il protocollo creato appositamente, si può chiamare il metodo di protocollo sul delegato e dismettere il popover nell’implementazione di quel metodo. Passando degli argomenti si può decidere di compiere un azione a seconda dell’argomento.

 

Da array a stringa, e viceversa

Inutile spiegare quanto utile può essere il creare un NSArray a partire da una stringa di elementi separati da un carattere speciale (come ad esempio per creare un file CSV) e ottenere il risultato inverso, partendo da un array per ottenere una serie di NSString.

Core Foundation di Apple possiede una coppia di metodi speculari che fanno proprio questo lavoro.

Si tratta di componentsSeparatedByString: che crea un NSArray separando una stringa in componenti tramite la stringa specificata.

La funzione inversa è componentsJoinedByString: che prende un NSArray e lo trasforma in una stringa in cui gli elementi dell’array sono concatenati dalla stringa specificata.