wake-up-neo.com

Was ist der beste Weg, um zwischen View Controllern zu kommunizieren?

Als Neuling in Objective-C, Cocoa und dem iPhone-Entwickler im Allgemeinen habe ich den starken Wunsch, die Sprache und die Frameworks optimal zu nutzen.

Eine der Ressourcen, die ich verwende, sind die Notizen der CS193P-Klasse von Stanford, die sie im Web hinterlassen haben. Es enthält Skript, Aufgaben und Beispielcode, und da der Kurs von Apple dev's gegeben wurde, halte ich ihn definitiv für "aus dem Maul des Pferdes".

Klasse Website:
http://www.stanford.edu/class/cs193p/cgi-bin/index.php

Vorlesung 08 befasst sich mit einer Aufgabe zum Erstellen einer UINavigationController-basierten App, bei der mehrere UIViewController auf den UINavigationController-Stapel verschoben wurden. So funktioniert der UINavigationController. Das ist logisch. Die Folie enthält jedoch einige strenge Warnungen zur Kommunikation zwischen Ihren UIViewControllern.

Ich zitiere aus diesen seriösen Folien:
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf

Seite 16/51:

So geben Sie keine Daten frei

  • Globale Variablen oder Singletons
    • Dies beinhaltet Ihren Anwendungsdelegierten
  • Direkte Abhängigkeiten machen Ihren Code weniger wiederverwendbar
    • Und schwieriger zu debuggen und zu testen

Okay. Damit bin ich fertig. Werfen Sie nicht blind alle Methoden, die für die Kommunikation zwischen dem Viewcontroller verwendet werden, in Ihren App-Delegaten und verweisen Sie auf die Viewcontroller-Instanzen in den App-Delegaten-Methoden. Fair 'Nuff.

Etwas später erfahren wir auf dieser Folie, was wir tun sollen .

Seite 18/51:

Best Practices für den Datenfluss

  • Finde genau heraus, was kommuniziert werden muss
  • Definieren Sie Eingabeparameter für Ihren View Controller
  • Verwenden Sie für die Kommunikation zur Sicherung der Hierarchie eine lose Kopplung
    • Definieren Sie eine generische Schnittstelle für Beobachter (wie Delegation)

Auf diese Folie folgt dann eine scheinbar platzhaltende Folie, auf der der Dozent anscheinend die Best Practices anhand eines Beispiels mit dem UIImagePickerController demonstriert. Ich wünschte, die Videos wären verfügbar! :(

Ok, also ... ich fürchte, mein Objc-Fu ist nicht so stark. Ich bin auch ein bisschen durch die letzte Zeile im obigen Zitat verwirrt. Ich habe meine ganze Zeit darüber gegoogelt und einen anständigen Artikel über die verschiedenen Methoden der Beobachtung/Benachrichtigung gefunden:
http://cocoawithlove.com/2008/06/five-approaches-to-listening-observing.html

Methode 5 zeigt sogar Delegierte als Methode an! Mit Ausnahme von .... Objekten kann jeweils nur ein Delegat festgelegt werden. Was muss ich also tun, wenn ich über eine Kommunikation mit mehreren Viewcontrollern verfüge?

Ok, das ist die aufgebaute Bande. Ich weiß, dass ich meine Kommunikationsmethoden im App-Delegaten durch Verweisen auf die mehreren Viewcontroller-Instanzen in meinem App-Delegaten leicht ausführen kann, aber ich möchte so etwas auf die richtige Weise tun.

Bitte helfen Sie mir, "das Richtige zu tun", indem Sie die folgenden Fragen beantworten:

  1. Wenn ich versuche, einen neuen Viewcontroller auf dem UINavigationController-Stapel zu pushen, , wer sollte diesen Push ausführen. Welche Klasse/Datei in meinem Code ist der richtige Ort?
  2. Wenn ich einen Teil der Daten (Wert einer iVar) in einem meiner UIViewController beeinflussen möchte, wenn ich mich in einem anderen UIViewController befinde, was ist der " Richtiger "Weg, dies zu tun?
  3. Geben Sie an, dass in einem Objekt immer nur ein Delegat festgelegt werden kann. Wie würde die Implementierung aussehen, wenn der Dozent sagt "Definieren Sie eine allgemeine Schnittstelle für Beobachter (wie Delegation)" . Ein Pseudocode-Beispiel wäre hier, wenn möglich, sehr hilfreich.
164
A Hopeful Soul

Das sind gute Fragen, und es ist großartig zu sehen, dass Sie diese Forschung betreiben und sich mit dem Lernen befassen, wie man es richtig macht, anstatt es einfach zusammen zu hacken.

First, ich stimme den vorherigen Antworten zu, die sich auf die Wichtigkeit konzentrieren, Daten in Modellobjekten zu platzieren, wenn dies angebracht ist (per das MVC-Entwurfsmuster). Normalerweise möchten Sie vermeiden, Statusinformationen in einen Controller einzufügen, es sei denn, es handelt sich ausschließlich um "Präsentations" -Daten.

Second Auf Seite 10 der Stanford-Präsentation finden Sie ein Beispiel für die programmgesteuerte Übertragung eines Controllers auf den Navigationscontroller . Ein Beispiel für diese "visuelle" Vorgehensweise mit Interface Builder finden Sie in dieses Tutorial .

Third und vielleicht am wichtigsten ist, dass die in der Stanford-Präsentation erwähnten "Best Practices" viel einfacher zu handhaben sind Verstehen Sie, wenn Sie über sie im Kontext des Entwurfsmusters "Abhängigkeitsinjektion" nachdenken. Kurz gesagt bedeutet dies, dass Ihr Controller die Objekte, die er für seine Arbeit benötigt (z. B. eine globale Variable referenzieren), nicht "nachschlagen" sollte. Stattdessen sollten Sie diese Abhängigkeiten immer in den Controller "einspeisen" (d. H. Die benötigten Objekte über Methoden übergeben).

Wenn Sie dem Abhängigkeitsinjektionsmuster folgen, ist Ihre Steuerung modular und wiederverwendbar. Und wenn Sie darüber nachdenken, woher die Stanford-Moderatoren kommen (d. H. Als Apple -Mitarbeiter, besteht ihre Aufgabe darin, Klassen zu erstellen, die leicht wiederverwendet werden können), haben Wiederverwendbarkeit und Modularität hohe Priorität. Alle empfohlenen Vorgehensweisen für die gemeinsame Nutzung von Daten sind Teil der Abhängigkeitsinjektion.

Das ist der Kern meiner Antwort. Im Folgenden wird ein Beispiel für die Verwendung des Abhängigkeitsinjektionsmusters mit einem Controller aufgeführt, falls dies hilfreich ist.

Beispiel für die Verwendung der Abhängigkeitsinjektion mit einem View-Controller

Angenommen, Sie erstellen einen Bildschirm, in dem mehrere Bücher aufgelistet sind. Der Benutzer kann Bücher auswählen, die er kaufen möchte, und dann auf die Schaltfläche "Kasse" tippen, um zum Kassenbildschirm zu gelangen.

Um dies zu erstellen, können Sie eine BookPickerViewController-Klasse erstellen, die die GUI-/Ansichtsobjekte steuert und anzeigt. Woher bekommt es alle Buchdaten? Nehmen wir an, das hängt von einem BookWarehouse-Objekt ab. Ihr Controller vermittelt also im Grunde genommen Daten zwischen einem Modellobjekt (BookWarehouse) und den GUI-/Ansichtsobjekten. Mit anderen Worten, BookPickerViewController DEPENDS für das BookWarehouse-Objekt.

Mach das nicht:

@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];
   ...
}

Stattdessen sollten die Abhängigkeiten folgendermaßen injiziert werden:

@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];
   ...
}

Wenn die Apple über die Verwendung des Delegierungsmusters sprechen, um "die Hierarchie zu sichern", sprechen sie immer noch über die Abhängigkeitsinjektion. Was soll der BookPickerViewController in diesem Beispiel tun, wenn der Benutzer seine Bücher ausgewählt hat und zum Auschecken bereit ist? Nun, das ist nicht wirklich seine Aufgabe. Es sollte diese Arbeit auf ein anderes Objekt DELEGIEREN, was bedeutet, dass es von einem anderen Objekt abhängt. Daher können wir unsere BookPickerViewController-Init-Methode wie folgt ändern:

@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];
   ...
}

Das Nettoergebnis all dessen ist, dass Sie mir Ihre BookPickerViewController-Klasse (und verwandte GUI/View-Objekte) geben können und ich sie problemlos in meiner eigenen Anwendung verwenden kann, vorausgesetzt, BookWarehouse und CheckoutController sind generische Schnittstellen (dh Protokolle), die ich implementieren kann :

@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];
}

Schließlich ist Ihr BookPickerController nicht nur wiederverwendbar, sondern auch einfacher zu testen.

-(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
   ...
}
224
Clint Harris

So etwas ist immer Geschmackssache.

Trotzdem bevorzuge ich immer die Koordination (# 2) über Modellobjekte. Der Ansichtscontroller der obersten Ebene lädt oder erstellt die benötigten Modelle, und jeder Ansichtscontroller legt Eigenschaften in seinen untergeordneten Controllern fest, um ihnen mitzuteilen, mit welchen Modellobjekten sie arbeiten müssen. Die meisten Änderungen werden mithilfe von NSNotificationCenter in der Hierarchie gesichert. Das Auslösen der Benachrichtigungen ist normalerweise im Modell selbst integriert.

Angenommen, ich habe eine App mit Konten und Transaktionen. Ich habe auch einen AccountListController, einen AccountController (der eine Kontoübersicht mit der Schaltfläche "Alle Transaktionen anzeigen" anzeigt), einen TransactionListController und einen TransactionController. AccountListController lädt eine Liste aller Konten und zeigt sie an. Wenn Sie auf ein Listenelement tippen, wird die Eigenschaft .account des AccountControllers festgelegt und der AccountController auf den Stapel verschoben. Wenn Sie auf die Schaltfläche "Alle Transaktionen anzeigen" tippen, lädt AccountController die Transaktionsliste, fügt sie in die .transactions-Eigenschaft des TransactionListController ein und verschiebt den TransactionListController auf den Stapel usw.

Wenn beispielsweise TransactionController die Transaktion bearbeitet, nimmt er die Änderung an seinem Transaktionsobjekt vor und ruft dann die Methode 'save' auf. 'save' sendet eine TransactionChangedNotification. Jeder andere Controller, der sich selbst aktualisieren muss, wenn sich die Transaktion ändert, würde die Benachrichtigung beobachten und sich selbst aktualisieren. TransactionListController würde vermutlich; AccountController und AccountListController können je nach dem, was sie versuchen, zu tun.

Für # 1 hatte ich in meinen frühen Apps eine Art displayModel: withNavigationController: -Methode im untergeordneten Controller, die die Dinge einrichten und den Controller auf den Stapel schieben würde. Aber als ich mich mit dem SDK wohler gefühlt habe, bin ich davon abgewichen und habe jetzt normalerweise die Eltern, die das Kind schieben.

Betrachten Sie für # 3 dieses Beispiel. Hier verwenden wir zwei Controller, AmountEditor und TextEditor, um zwei Eigenschaften einer Transaktion zu bearbeiten. Die Bearbeiter sollten die bearbeitete Transaktion nicht tatsächlich speichern, da der Benutzer entscheiden könnte, die Transaktion abzubrechen. Stattdessen nehmen beide ihren übergeordneten Controller als Stellvertreter und rufen eine Methode auf, die angibt, ob sie etwas geändert haben.

@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

Und jetzt ein paar Methoden von 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];
}

Zu beachten ist, dass wir ein generisches Protokoll definiert haben, mit dem die Redakteure mit ihren Controllern kommunizieren können. Auf diese Weise können wir die Editoren in einem anderen Teil der Anwendung wiederverwenden. (Möglicherweise können Konten auch Notizen enthalten.) Das EditorDelegate-Protokoll kann natürlich mehrere Methoden enthalten. in diesem Fall ist das die einzig notwendige.

15

Angenommen, es gibt zwei Klassen A und B.

instanz der Klasse A ist

Eine Instanz;

klasse A macht und Instanz der Klasse B, als

BInstanz;

Und in Ihrer Logik der Klasse B müssen Sie irgendwo eine Methode der Klasse A kommunizieren oder auslösen.

1) Falscher Weg

Sie könnten die Instanz an die Instanz übergeben. Platzieren Sie nun den Aufruf der gewünschten Methode [Instanz Methodenname] an der gewünschten Stelle in Instanz.

Dies hätte Ihren Zweck erfüllt, aber während der Freigabe hätte ein Speicher gesperrt und nicht freigegeben.

Wie?

Wenn Sie die Instanz an die Instanz übergeben haben, haben wir die Anzahl der Instanzen um 1 erhöht. Bei der Freigabe der Instanz wird der Speicher blockiert, da die Instanz niemals auf 0 gesetzt werden kann, da die Instanz selbst ein Objekt der Instanz ist.

Da eine Instanz stecken bleibt, bleibt auch der Speicher der Instanz stecken (durchgesickert). Selbst nachdem die Zuordnung von aInstance zu einem späteren Zeitpunkt aufgehoben wurde, wird der Speicher ebenfalls blockiert, da bInstance nicht freigegeben werden kann und bInstance eine Klassenvariable von aInstance ist.

2) Richtiger Weg

Durch die Definition von aInstance als Delegierter von bInstance kommt es zu keiner Änderung der Anzahl oder zu keiner Verwicklung von aInstance in den Speicher.

bInstance kann die in aInstance enthaltenen Delegate-Methoden frei aufrufen. Bei der Freigabe von bInstance werden alle Variablen selbst erstellt und freigegeben. Da bei der Freigabe von aInstance keine Verwicklung von aInstance in bInstance vorliegt, wird diese sauber freigegeben.

0
rd_

Ich sehe dein Problem ..

Was passiert ist, ist, dass jemand die Idee der MVC-Architektur verwirrt hat.

MVC besteht aus drei Teilen. Modelle, Ansichten und Steuerungen. Das angegebene Problem scheint zwei von ihnen ohne triftigen Grund kombiniert zu haben. Ansichten und Controller sind separate logische Elemente.

also ... du willst nicht mehrere view-controller haben ..

sie möchten mehrere Ansichten und einen Controller, der zwischen ihnen wählt. (Sie könnten auch mehrere Controller haben, wenn Sie mehrere Anwendungen haben)

ansichten sollten NICHT Entscheidungen treffen. Die Controller sollten das tun. Daher die Trennung von Aufgaben, Logik und Möglichkeiten, Ihr Leben leichter zu machen.

Vergewissern Sie sich also, dass Ihre Ansicht genau das tut, und geben Sie einen guten Überblick über die Daten. Lassen Sie Ihren Controller entscheiden, was mit den Daten geschehen soll und welche Ansicht verwendet werden soll.

(und wenn wir über Daten sprechen, sprechen wir über das Modell ... eine nette Standardmethode, um gespeichert, abgerufen, modifiziert zu werden ... eine weitere separate Logik, die wir wegpacken und vergessen können)

0
Bingy