In questo articolo affronteremo un problema che è stato spesso oggetto di richieste sul forum, vedremo infatti quali sono i modi in cui gli oggetti possono interagire e comunicare tra di loro.
Sul nostro forum sono state poste spesso domande come: “come faccio per passare una variabile da una UIView A ad una UIView B?” oppure “Dalla UITable A creo una UIView B e vorrei che visualizzasse alcuni dati presi dalla UITable A…”
Queste domande sono solo casi particolari di comunicazione tra oggetti e la risposta è “devi utilizzare uno dei modi possibili per far comunicare l’oggetto A con l’oggetto B”. Perché non bisogna dimenticare che UITable, UIVIew.. sono pur sempre oggetti, magari un po’ particolari perché dotati di interfaccia grafica, ma pur sempre oggetti come un NSArray o una NSString.
Quanti modi di comunicare?
Prima di tutto suddividerei i modi di comunicare in due categorie: la comunicazione diretta e la comunicazione indiretta. Vedremo con alcuni esempi e vantaggi / svantaggi di entrambe cosicchè possiate decidere, di volta in volta, la vostra strategia.
Comunicazione diretta
La comunicazione diretta si ha quanto un oggetto A comunica direttamente ad un oggetto B senza l’utilizzo di intermediari.
Un esempio di comunicazione diretta la incontriamo spessissimo nel nostro lavoro, come si vede in queste righe di codice:
UIView *sampleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
[sampleView setBackgroundColor:[UIColor grayColor]];
Qui vediamo due esempi di comunicazione diretta, nel primo inviamo il messaggio initWithFrame: all’oggetto restituito dal metodo alloc: mentre nel secondo inviamo il messaggio setBackgoundColor: all’oggetto sampleView.
Anche se può sembrare scontato è bene sottolineare che questo è possibile solo perché l’oggetto che contiene il codice di questo esempio “sa” qual è l’indirizzo dell’oggetto sampleView, o per usare un termine più adatto “possiede un riferimento a sampleView”.
Facciamo un secondo esempio, supponiamo di aver creato due nuove classi: Mittente e Destinatario, entrambe per comodità subclass di UIView
Mittente *alice = [[Mittente alloc] init];
Destinatario *bob = [[Destinatario alloc] init];
In questo caso alice potrà comunicare con bob? La risposta è no perché così come li abbiamo creati alice non ha nessun riferimento a bob e quindi non può inviargli nessun messaggio.
Per far sì che alice possa comunicare direttamente con bob abbiamo due possibilità, la prima è quella di fornirle un riferimento a bob come parametro di un metodo, ad esempio potremmo aggiungere alla classe Mittente questo metodo:
-(void)rendiTrasparente:(Destinatario *)dest {
[dest setBackgroundColor:[UIColor clearColor]];
}
quindi basterebbe richiamare:
[alice rendiTrasparente:bob];
per far inviare ad alice un messaggio a bob chiedendogli di impostare il suo colore di sfondo a trasparente.
Da notare che in questo caso alice da sola non sa comunicare con bob, quindi non è un oggetto che opera in maniera indipendente, potremmo dire che è un oggetto che offre il servizio di invio messaggi a chi ha un riferimento ad un oggetto di tipo Destinatario.
La seconda possibilità è forse più complessa e rende alice in grado di lavorare in maniera indipendente. Per far questo possiamo memorizzare all’interno della classe Mittente un riferimento all’oggetto Destinatario.
Modifichiamo quindi la classe Mittente aggiungendo una variabile di istanza di tipo Destinatario, e la settiamo subito dopo aver costruito l’oggetto:
Destinatario *bob = [[Destinatario alloc] init];
[alice setDestinatario:bob];
così facendo alice possiede un riferimento a bob e può utilizzarlo in maniera indipendente in un metodo come:
-(void)rendiTrasparente {
[self.destinatario setBackgroundColor:[UIColor clearColor]];
}
Come si può ben vedere il metodo non accetta più nessun parametro quindi l’unico modo di modifirane il comportamento è quello di cambiare il valore della variabile destinatario.
Comunicazione indiretta
In alcuni casi un oggetto può comunicare con un altro anche se non ne possiede un riferimento diretto. Questo può avvenire grazie ad un oggetto che assuma il ruolo di intermediario.
È necessario che i due oggetti che vogliono comunicare utilizzino lo stesso intermediario altrimenti la comunicazione non può avvenire.
Le classi singleton si prestano bene a tale scopo perché all’interno di un’applicazione può essere presente solo una singola istanza della classe quindi non bisogna preoccuparsi di condividere un riferimento ad una particolare istanza.
Nulla vieta di costruirsi le proprie classi singleton che facciano da intermediario, ma iOS ce ne mette due a disposizione che possono svolgere questo compito: UIApplication e NSNotificationCenter;
AppDelegate
Possiamo ottenere un riferimento all’istanza singleton delle classe UIApplication tramite:
[UIApplication sharedApplication]
noi siamo però interessati non alla classe in sé, ma alla sua proprietà delegate che è un riferimento alla classe
Supponiamo quindi che alice voglia comunicare a bob un numero intero ma non siano direttamente correlati, si può aggiungere all’AppDelegate una variabile di tipo intero e, proprio per la visibilità globale di questa classe, alice potrà memorizzarvi il valore che vuol comunicare a bob e bob potrà leggerlo.
La classe Mittente avrà quindi un metodo come:
-(void)memorizzaValore {
[[[UIApplication sharedApplication] delegate] setVariabileInt:42];
}
mentre la classe Destinatario avrà un metodo come:
-(void)leggiValore {
int k;
k = [[[UIApplication sharedApplication] delegate] variabileInt];
}
Io non sono un grande fan di questo approccio, perché ritengo che l’utilizzo dell’appdelegate come punto di convergenza tra oggetti diversi sia spesso una necessità dettata da una progettazione non ottimale, inoltre rovina la riusabilità del codice e tende a rendere l’applicazione più confusa e meno gestibile; é innegabile, però, che si tratti di un approccio piuttosto diffuso e quindi mi è sembrato giusto parnarne.
NSNotificationCenter
L’utilizzo della classe NSNotificationCenter è un altro modo per far comunicare oggetti distanti e che non posseggono un riferimento diretto.
L’utilizzo è piuttosto semplice e risponde al design pattern “observer” (http://it.wikipedia.org/wiki/Observer_pattern).
L’oggetto che vuole ricevere una notifica si mette in ascolto di un particolare evento in questo modo:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(receiveTestNotification:)
name:@"TestNotification"
object:nil];
Cosa fa questo codice? Stiamo chiedendo alla classe singleton NSNotificationCenter di aggiungere l’oggetto self alla lista degli oggetti interessati alle notifiche “TestNotification” e quando questa arriverà verrà invocato il metodo receiveTestNotification:
Quando l’oggetto non è più interessato a ricevere notifiche può informare il NSNotification center in questo modo:
[[NSNotificationCenter defaultCenter] removeObserver:self];
il quale provvederà a toglierlo dalla lista degli observer.
L’oggetto che vuole inviare la notifica dovrà semplicemente utilizzare questo codice:
[[NSNotificationCenter defaultCenter] postNotificationName:@"TestNotification" object:nil];
per comunicare con tutti gli observer che qualcosa è avvenuto.
Un tipico utilizzo delle notifiche si ha con le UITableView. Nei progetti piccoli il viewcontroller dove risiede la tabella è anche il suo datasource, ma in progetti più grandi il datasource è una classe separata. In questo caso come può il datasource forzare un [table reloadData] se non ha un riferimento diretto alla tabella? Può usare una notifica per comunicare indirettamente alla tabella che è avvenuto un cambiamento nella sorgente dati, sarà poi la tabella a decidere cosa fare di questa notifica.
Conclusioni
Abbiamo visto diversi modi di far parlare gli oggetti tra di loro, nessuno di questi è migliore in senso assoluto rispetto agli altri, ciascuno ha le sue caratteristiche e i suoi casi d’uso, il mio consiglio è quello di cercare di farsi una visione chiara dell’applicazione nella sua interezza, immaginando gli oggetti quasi come attori su un palcoscenico, ciascuno ad interpretare il suo ruolo, il nostro compito è quello di sovraninterndere e coordinare la collaborazione tra gli attori, assegnando a ciascuno di essi la giusta parte senza mescolare i ruoli tra loro.
buona programmazione!
5 Responses to “L#020 – Far comunicare oggetti diversi in Objective-C: quale metodo scegliere?”
6 Novembre 2011
PiersoftOttimo tutorial.
Dato che avete parlato anche di metodi indiretti, io aggiungerei nsuserdefault
6 Novembre 2011
ignaziocGrazie!
Per quanto riguarda NSUserDefault…beh diciamo che può essere considerato un sistema di comunicazione indiretto solo se inseriamo nella categoria anche scrivere sul filesystem…
C’è davvero un grande assente in questo articolo, ed è il Key Value Observer, una tecnica un tantino più avanzata che sfutta il concetto di keypath.
19 Dicembre 2011
LuigiHo letto l’esempio ma forse mi è poco chiaro.
Io utilizzo una modalità diversa ( per alcuni aspetti ) simile alla tua
Supponiamo di avere una Classe A ed una Classe B
La classe B viene, ad esempio, richiamata alla pressione di una cella presente in una tableView della classe A.
Tu come faresti?
Io nel delegate istanzio un oggetto di tipo B mostrandolo poi facendo la push sul navigationController dell’oggetto inizializzato con il proprio Nib
Non so se sono stato chiaro 😀
20 Dicembre 2011
ignaziocnon ho capito il problema.
Se la classe B ha bisogno di dire qualcosa alla precisa istanza della classe A che l’ha creata allora setterai una variabile nella classe B che punta all’oggetto A e se vuoi lo chiami delegate per chiarezza.
Così da B puoi richiamare qualcosa come [self.delegate metodo];
26 Dicembre 2011
LuigiAllora secondo me stiamo parlando di due problemi diversi
Spiego meglio quello a cui io faccio riferimento facendo un esempio pratico
Classe A – TableView con relative celle popolate con del testo
Classe B – Dettaglio dell’elemento relativo alla cella tableView
Un esempio classico potrebbe essere Lista News ( Classe A ) e dettaglio News ( Classe B )
Anche in questo caso hai due oggetti che comunicano tra loro, io nel delegate di UITableView ( didSelectionRow ) dichiaro un’oggetto di tipo Classe B e a quell’istanza passo i dati relativi al dettaglio news
Ti trovi?