2009-02-20 14 views
162

Essendo nuovo per ogg-c, cacao e iPhone dev in generale, ho un forte desiderio di ottenere il massimo dal linguaggio e dalle strutture.Qual è il modo migliore per comunicare tra i controller di visualizzazione?

Una delle risorse che sto utilizzando sono le note di classe CS193P di Stanford che hanno lasciato sul web. Include note di lezione, compiti e codice di esempio, e poiché il corso è stato dato da Apple Dev's, lo considero sicuramente "dalla bocca del cavallo".

Classe Sito web:
http://www.stanford.edu/class/cs193p/cgi-bin/index.php

Lecture 08 è legato a un incarico di costruire un'applicazione basata UINavigationController che dispone di più UIViewControllers inseriti nello stack UINavigationController. È così che funziona UINavigationController. È logico. Tuttavia, ci sono alcuni avvisi severi nella diapositiva sulla comunicazione tra i tuoi UIViewControllers.

ho intenzione di citare questo grave di diapositive:
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf

Pagina 16/51:

Come non condividere i dati

  • variabili globali o singoletti
    • Questo include il applicazione delegato
  • dipendenze dirette rendere il codice meno riutilizzabili
    • e più difficili da eseguire il debug & prova

Ok. Sono giù con quello. Non gettare alla cieca tutti i metodi che verranno utilizzati per comunicare tra il viewcontroller nel delegato dell'app e fare riferimento alle istanze di viewcontroller nei metodi di delega dell'app. Fair 'nuff.

Un po 'più avanti, otteniamo questa diapositiva dicendoci cosa dobbiamo fare do.

Pagina 18/51:

Best Practices per il flusso di dati

  • Capire esattamente ciò che deve essere comunicato
  • Definire parametri di ingresso per il controller della vista
  • Per la comunicazione di backup della gerarchia, utilizzare cou loose pling
    • definire un'interfaccia generica per gli osservatori (come delegazione)

Questa diapositiva è poi seguito da quello che sembra essere una diapositiva segnaposto in cui il docente poi a quanto pare dimostra il migliore pratica usando un esempio con UIImagePickerController. Vorrei che i video fossero disponibili! :(

Ok, quindi ... temo che il mio objc-fu non sia così forte, sono anche un po 'confuso dall'ultima riga della frase precedente. googling su questo e ho trovato quello che sembra essere un articolo decente parlando dei vari metodi di osservazione tecniche/notifica:!
http://cocoawithlove.com/2008/06/five-approaches-to-listening-observing.html

metodo # 5 indica anche i delegati come metodo Tranne .... oggetti possono impostare solo un delegato alla volta Quindi, quando ho più comunicazioni con il viewcontroller, cosa devo fare?

Ok, questa è la gang di installazione, so che posso facilmente eseguire i miei metodi di comunicazione. n l'app delegata per riferimento è le istanze multiple di viewcontroller nel mio appdelegate ma voglio fare questo genere di cose nel modo a destra in questo modo .

Please help me "fare la cosa giusta", rispondendo alle seguenti domande:

  1. Quando sto cercando di spingere un nuovo viewcontroller sullo stack UINavigationController, che dovrebbe fare questa spinta. Quale classe/file nel mio codice è la posizione corretta?
  2. Quando voglio modificare alcuni dati (valore di un iVar) in uno dei miei UIViewControllers quando sono in un diverso UIViewController, qual è il modo "giusto" per farlo?
  3. Diamo che possiamo avere un solo set delegato alla volta in un oggetto, come sarebbe l'implementazione quando il docente dice "Definire un'interfaccia generica per gli osservatori (come la delega)". Un esempio di pseudocodice sarebbe terribilmente utile qui, se possibile.
+0

Alcune di queste sono trattate in questo articolo da Apple - http://developer.apple.com/library/ios/# featuredarticles/ViewControllerPGforiPhoneOS/ManagingDataFlowBetweenViewControllers/ManagingDataFlowBetweenViewControllers.html –

+0

Solo una breve osservazione: i video per la classe Stanford CS193P sono ora disponibili tramite iTunes U. L'ultimo (2012-13) può essere visto su https://itunes.apple.com/us/course/coding-together-developing/id593208016 e mi aspetto che i video e le diapositive future saranno annunciati all'indirizzo http://cs193p.stanford.edu –

risposta

15

Questo genere di cose è sempre una questione di gusti.

Detto questo, preferisco sempre fare il mio coordinamento (n. 2) tramite gli oggetti del modello. Il controller di visualizzazione di livello superiore carica o crea i modelli necessari e ciascun controller di visualizzazione imposta le proprietà nei relativi controller figlio per comunicare loro quali oggetti modello devono essere utilizzati.La maggior parte delle modifiche viene comunicata al backup della gerarchia utilizzando NSNotificationCenter; l'attivazione delle notifiche è solitamente integrata nel modello stesso.

Per esempio, supponiamo di avere un app con i conti e transazioni. Ho anche un AccountListController, un AccountController (che mostra una sintesi account con uno "spettacolo tutte le transazioni" button), un TransactionListController e un TransactionController. AccountListController carica un elenco di tutti gli account e li visualizza. Quando si tocca un elemento della lista, si imposta la proprietà .account della sua AccountController e spinge l'AccountController nello stack. Quando si tocca il pulsante "Mostra tutte le transazioni", AccountController carica la lista delle transazioni, la mette nella sua proprietà .transactions di TransactionListController, e spinge il TransactionListController nello stack, e così via.

Se, per esempio, TransactionController modifica la transazione, rende il cambiamento nel suo oggetto di transazione e poi chiama il suo metodo di 'salvare'. 'save' invia una TransactionChangedNotification. Qualsiasi altro controller che deve aggiornare se stesso quando la transazione cambia osservare la notifica e aggiornarsi. TransactionListController sarebbe presumibilmente; AccountController e AccountListController potrebbero, in base a ciò che stavano cercando di fare.

Per il numero 1, nelle mie prime app avevo una sorta di displayModel: withNavigationController: metodo nel controller figlio che impostava le cose e spingeva il controller nello stack. Ma siccome sono diventato più a mio agio con l'SDK, mi sono allontanato da questo, e ora di solito il genitore spinge il bambino.

Per 3 #, considerare questo esempio. Qui stiamo usando due controller, AmountEditor e TextEditor, per modificare due proprietà di una Transazione. Gli editor non dovrebbero effettivamente salvare la transazione in fase di modifica, poiché l'utente potrebbe decidere di abbandonare la transazione. Quindi, invece, entrambi prendono il loro controllore genitore come delegato e chiamano un metodo su di esso dicendo se hanno cambiato qualcosa.

@class Editor; 
@protocol EditorDelegate 
// called when you're finished. updated = YES for 'save' button, NO for 'cancel' 
- (void)editor:(Editor*)editor finishedEditingModel:(id)model updated:(BOOL)updated; 
@end 

// this is an abstract class 
@interface Editor : UIViewController { 
    id model; 
    id <EditorDelegate> delegate; 
} 
@property (retain) Model * model; 
@property (assign) id <EditorDelegate> delegate; 

...define methods here... 
@end 

@interface AmountEditor : Editor 
...define interface here... 
@end 

@interface TextEditor : Editor 
...define interface here... 
@end 

// TransactionController shows the transaction's details in a table view 
@interface TransactionController : UITableViewController <EditorDelegate> { 
    AmountEditor * amountEditor; 
    TextEditor * textEditor; 
    Transaction * transaction; 
} 
...properties and methods here... 
@end 

Ed ora alcuni metodi da TransactionController:

- (void)viewDidLoad { 
    amountEditor.delegate = self; 
    textEditor.delegate = self; 
} 

- (void)editAmount { 
    amountEditor.model = self.transaction; 
    [self.navigationController pushViewController:amountEditor animated:YES]; 
} 

- (void)editNote { 
    textEditor.model = self.transaction; 
    [self.navigationController pushViewController:textEditor animated:YES]; 
} 

- (void)editor:(Editor*)editor finishedEditingModel:(id)model updated:(BOOL)updated { 
    if(updated) { 
     [self.tableView reloadData]; 
    } 

    [self.navigationController popViewControllerAnimated:YES]; 
} 

La cosa da notare è che abbiamo definito un protocollo generico che editori possono utilizzare per comunicare con il loro controllore possedere. In tal modo, possiamo riutilizzare gli editor in un'altra parte dell'applicazione. (Forse anche gli account possono avere delle note.) Naturalmente, il protocollo EditorDelegate potrebbe contenere più di un metodo; in questo caso è l'unico necessario.

+1

Si suppone che questo funzioni così com'è? Sto avendo problemi con il membro 'Editor.delegate'. Nel mio metodo 'viewDidLoad', sto ricevendo 'Proprietà' delegato 'non trovato ...'. Non sono sicuro di aver rovinato qualcos'altro. O se questo è abbreviato per brevità. – Jeff

+0

Questo è ora un codice piuttosto vecchio, scritto in uno stile precedente con convenzioni precedenti. Non vorrei copiarlo e incollarlo direttamente sul tuo progetto; Proverò solo a imparare dagli schemi. –

+0

Gotcha. Questo è esattamente quello che volevo sapere. L'ho fatto funzionare con alcune modifiche, ma ero un po 'preoccupato che non corrispondesse testualmente. – Jeff

0

vedo il tuo problema ..

Ciò che è accaduto è che qualcuno ha confuso idea di architettura MVC.

MVC ha tre parti .. modelli, viste e controller. Il problema indicato sembra averne combinati due senza una buona ragione. viste e controller sono pezzi di logica separati.

così ... non si desidera avere più view-controller ..

si desidera avere viste multiple, e un controller che sceglie tra di loro. (potresti anche avere più controller, se hai più applicazioni)

le visualizzazioni NON dovrebbero prendere decisioni. Il controller (i) dovrebbe farlo. Da qui la separazione dei compiti, della logica e dei modi per semplificarti la vita.

Quindi .. assicurati che la tua vista faccia proprio questo, metta in bella evidenza i dati. lascia che il tuo controller decida cosa fare con i dati e quale vista usare.

(e quando parliamo di dati, stiamo parlando del modello ... un modo standard di essere bella storred, si accede, modificato .. un altro pezzo separato di logica che siamo in grado di spartire via e dimenticare)

223

Queste sono buone domande, ed è bello vedere che stai facendo questa ricerca e sembra preoccuparsi di imparare a "fare bene" invece di hackerarlo insieme.

primo, sono d'accordo con le risposte precedenti che si concentrano su l'importanza di mettere i dati nel modello di oggetti, se del caso (per il modello di progettazione MVC). Di solito si vuole evitare di inserire informazioni di stato all'interno di un controller, a meno che non si tratti di dati strettamente "di presentazione".

In secondo luogo, vedere a pagina 10 della presentazione Stanford per un esempio di come spingere un controller di programmazione sul controller di navigazione. Per un esempio di come "visivamente" utilizzare Interface Builder, dai un'occhiata a this tutorial.

Terzo, e forse più importante, notare che le "buone pratiche" di cui la presentazione di Stanford sono molto più facili da capire se pensi di loro nel contesto del modello di progettazione "iniezione di dipendenza". In poche parole, ciò significa che il controller non deve "cercare" gli oggetti di cui ha bisogno per svolgere il proprio lavoro (ad esempio, fare riferimento a una variabile globale). Invece, dovresti sempre "iniettare" queste dipendenze nel controller (ad esempio, passare gli oggetti necessari tramite i metodi).

Se si segue lo schema di iniezione delle dipendenze, il controller sarà modulare e riutilizzabile. E se si pensa a dove vengono i presentatori di Stanford (vale a dire, come dipendenti Apple il loro compito è quello di creare classi che possano essere facilmente riutilizzate), la riusabilità e la modularità sono priorità elevate. Tutte le migliori pratiche citate per la condivisione dei dati fanno parte dell'iniezione delle dipendenze.

Questo è l'essenza della mia risposta. Includerò un esempio di utilizzo del modello di iniezione delle dipendenze con un controller di seguito nel caso sia utile.

Esempio di utilizzo Dependency Injection con un View Controller

Diciamo che si sta costruendo una schermata in cui sono elencati diversi libri. L'utente può scegliere i libri che desidera acquistare, quindi toccare un pulsante "checkout" per andare alla schermata di checkout.

Per creare questo, è possibile creare una classe BookPickerViewController che controlli e visualizza gli oggetti di visualizzazione/GUI. Dove prenderà tutti i dati del libro? Diciamo che dipende da un oggetto BookWarehouse per quello. Quindi ora il controller è fondamentalmente un intermediario tra un oggetto modello (BookWarehouse) e la GUI/vista oggetti. In altre parole, BookPickerViewController DEPENDS sull'oggetto BookWarehouse.

Non fare questo:

@implementation BookPickerViewController 

-(void) doSomething { 
    // I need to do something with the BookWarehouse so I'm going to look it up 
    // using the BookWarehouse class method (comparable to a global variable) 
    BookWarehouse *warehouse = [BookWarehouse getSingleton]; 
    ... 
} 

Invece, le dipendenze deve essere iniettato in questo modo:

@implementation BookPickerViewController 

-(void) initWithWarehouse: (BookWarehouse*)warehouse { 
    // myBookWarehouse is an instance variable 
    myBookWarehouse = warehouse; 
    [myBookWarehouse retain]; 
} 

-(void) doSomething { 
    // I need to do something with the BookWarehouse object which was 
    // injected for me 
    [myBookWarehouse listBooks]; 
    ... 
} 

Quando i ragazzi di Apple stanno parlando utilizzando il modello delegazione di "comunicare back up la gerarchia, "stanno ancora parlando dell'iniezione di dipendenza.In questo esempio, cosa dovrebbe fare BookPickerViewController una volta che l'utente ha selezionato i suoi libri ed è pronto per il check-out? Bene, non è proprio il suo lavoro. DELEGARE che funziona con qualche altro oggetto, il che significa che DEPENDE su un altro oggetto. Così potremmo modificare il nostro metodo init BookPickerViewController come segue:

@implementation BookPickerViewController 

-(void) initWithWarehouse: (BookWarehouse*)warehouse 
     andCheckoutController:(CheckoutController*)checkoutController 
{ 
    myBookWarehouse = warehouse; 
    myCheckoutController = checkoutController; 
} 

-(void) handleCheckout { 
    // We've collected the user's book picks in a "bookPicks" variable 
    [myCheckoutController handleCheckout: bookPicks]; 
    ... 
} 

Il risultato netto di tutto questo è che mi puoi dare la tua classe BookPickerViewController (e GUI relativo/oggetti vista) e ho può facilmente utilizzare nel mio applicazione, assumendo BookWarehouse e CheckoutController sono interfacce generiche (cioè protocolli) che posso realizzare:

@interface MyBookWarehouse : NSObject <BookWarehouse> { ... } @end 
@implementation MyBookWarehouse { ... } @end 

@interface MyCheckoutController : NSObject <CheckoutController> { ... } @end 
@implementation MyCheckoutController { ... } @end 

... 

-(void) applicationDidFinishLoading { 
    MyBookWarehouse *myWarehouse = [[MyBookWarehouse alloc]init]; 
    MyCheckoutController *myCheckout = [[MyCheckoutController alloc]init]; 

    BookPickerViewController *bookPicker = [[BookPickerViewController alloc] 
             initWithWarehouse:myWarehouse 
             andCheckoutController:myCheckout]; 
    ... 
    [window addSubview:[bookPicker view]]; 
    [window makeKeyAndVisible]; 
} 

Infine, non solo è la BookPickerController riutilizzabile, ma anche più facile da testare.

-(void) testBookPickerController { 
    MockBookWarehouse *myWarehouse = [[MockBookWarehouse alloc]init]; 
    MockCheckoutController *myCheckout = [[MockCheckoutController alloc]init]; 

    BookPickerViewController *bookPicker = [[BookPickerViewController alloc] initWithWarehouse:myWarehouse andCheckoutController:myCheckout]; 
    ... 
    [bookPicker handleCheckout]; 

    // Do stuff to verify that BookPickerViewController correctly called 
    // MockCheckoutController's handleCheckout: method and passed it a valid 
    // list of books 
    ... 
} 
+19

Quando vedo domande (e risposte) come questa, realizzate con tanta cura, non posso fare a meno di sorridere. Complimenti meritati al nostro intrepido interlocutore ea voi !! Nel frattempo, volevo condividere un link aggiornato per il pratico collegamento a invasivecode.com cui facevi riferimento nel tuo secondo punto: http://www.invasivecode.com/2009/09/implementing-a-navigation-controller-uinavigationcontroller-with-interface -builder/- Grazie ancora per aver condiviso i tuoi approfondimenti e le migliori pratiche, oltre a supportarli con esempi! –

+0

Sono d'accordo. La domanda era ben formata e la risposta era semplicemente fantastica. Piuttosto che avere una risposta tecnica, includeva anche qualche psicologia dietro a come/perché è implementata usando DI. Grazie! +1 su –

+0

Che cosa succede se si desidera utilizzare BookPickerController per selezionare un libro per una lista di desideri o uno dei possibili motivi per la raccolta di libri. Utilizzeresti comunque l'approccio dell'interfaccia CheckoutController (forse rinominato in qualcosa come BookSelectionController) o forse utilizzer NSNotificationCenter? – Les

0

Supponiamo che ci siano due classi A e B.

esempio di classe A è

A AINSTANCE;

classe A fa e l'istanza di classe B, come

B bInstance;

E nella logica della classe B, da qualche parte si sono tenuti a comunicare o innescare un metodo della classe A.

1) Wrong way

Si potrebbe passare l'AINSTANCE a bInstance. posizionare ora la chiamata del metodo desiderato [a method method] dalla posizione desiderata in bInstance.

Questo sarebbe servito al tuo scopo, ma mentre il rilascio avrebbe portato a una memoria bloccata e non liberata.

Come?

Quando superato l'AINSTANCE a bInstance, abbiamo aumentato il retainCount di AINSTANCE di 1. Quando deallocando bInstance, avremo memoria bloccato perché AINSTANCE può mai essere portata a 0 retainCount dalla ragione è che bInstance bInstance stessa è un oggetto di una sostanza.

Inoltre, a causa di una sostanza bloccata, anche la memoria di bInstance sarà bloccata (trapelata). Quindi, anche dopo aver disassegnato una stessa Instance quando è arrivata l'ora, anche la sua memoria sarà bloccata perché bInstance non può essere liberato e bInstance è una variabile di classe di aInstance.

2) Modo giusto

Definendo AINSTANCE come delegato del bInstance, non ci sarà alcun cambiamento retainCount o la memoria di entanglement AINSTANCE.

bInstance sarà possibile invocare liberamente i metodi delegati che si trovano nell'Instance. Nella deallocazione di bInstance, tutte le variabili saranno create e verranno rilasciate Nella deallocazione di una sostanza, poiché non vi è alcun entanglement di aInstance in bInstance, verrà rilasciato in modo pulito.

Problemi correlati