objective-c - Quelle est la meilleure façon de communiquer entre les contrôleurs de vue?




iphone cocoa-touch delegates key-value-observing (5)

Ce sont de bonnes questions, et c'est génial de voir que vous faites cette recherche et semblent soucieux d'apprendre comment «faire les choses correctement» au lieu de simplement le pirater ensemble.

Tout d'abord , je suis d'accord avec les réponses précédentes qui mettent l'accent sur l'importance de mettre des données dans les objets du modèle lorsque cela est approprié (selon le modèle de conception MVC). Habituellement, vous voulez éviter de mettre des informations d'état à l'intérieur d'un contrôleur, sauf s'il s'agit de données strictement "de présentation".

Deuxièmement , voir la page 10 de la présentation de Stanford pour un exemple de la façon de pousser par programmation un contrôleur sur le contrôleur de navigation. Pour un exemple de la façon «visuelle» d'utiliser Interface Builder, jetez un oeil à ce tutoriel .

Troisièmement , et peut-être le plus important, notez que les «meilleures pratiques» mentionnées dans la présentation de Stanford sont beaucoup plus faciles à comprendre si vous pensez à eux dans le contexte du modèle de conception «injection de dépendance». En un mot, cela signifie que votre contrôleur ne doit pas "rechercher" les objets dont il a besoin pour faire son travail (par exemple, référencer une variable globale). Au lieu de cela, vous devriez toujours "injecter" ces dépendances dans le contrôleur (c'est-à-dire, transmettre les objets dont il a besoin via les méthodes).

Si vous suivez le modèle d'injection de dépendance, votre contrôleur sera modulaire et réutilisable. Et si vous pensez à l'origine des présentateurs de Stanford (en tant qu'employés d'Apple, leur travail consiste à créer des classes facilement réutilisables), la réutilisabilité et la modularité sont des priorités. Toutes les meilleures pratiques mentionnées pour le partage de données font partie de l'injection de dépendance.

C'est l'essentiel de ma réponse. Je vais inclure un exemple d'utilisation du schéma d'injection de dépendance avec un contrôleur ci-dessous au cas où cela serait utile.

Exemple d'utilisation de l'injection de dépendances avec un contrôleur de vue

Disons que vous construisez un écran dans lequel plusieurs livres sont répertoriés. L'utilisateur peut choisir les livres qu'il veut acheter, puis appuyez sur un bouton "checkout" pour aller à l'écran de paiement.

Pour cela, vous pouvez créer une classe BookPickerViewController qui contrôle et affiche les objets GUI / vue. Où obtiendra-t-il toutes les données du livre? Disons que cela dépend d'un objet BookWarehouse pour cela. Alors maintenant votre contrôleur est en train de négocier des données entre un objet modèle (BookWarehouse) et les objets GUI / view. En d'autres termes, BookPickerViewController DÉPEND de l'objet BookWarehouse.

Ne fais pas ça:

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

Au lieu de cela, les dépendances doivent être injectées comme ceci:

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

Quand les gars d'Apple parlent d'utiliser le modèle de délégation pour «communiquer en arrière dans la hiérarchie», ils parlent toujours d'injection de dépendance. Dans cet exemple, que doit faire le BookPickerViewController une fois que l'utilisateur a choisi ses livres et qu'il est prêt à partir? Eh bien, ce n'est pas vraiment son boulot. Il devrait DELEGATER ce travail à un autre objet, ce qui signifie qu'il DÉPEND sur un autre objet. Nous pourrions donc modifier notre méthode d'initialisation BookPickerViewController comme suit:

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

Le résultat net de tout cela est que vous pouvez me donner votre classe BookPickerViewController (et les objets GUI / view associés) et je peux facilement l'utiliser dans ma propre application, en supposant que BookWarehouse et CheckoutController sont des interfaces génériques (ie, protocoles) que je peux implémenter :

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

Enfin, non seulement votre BookPickerController est-il réutilisable mais aussi plus facile à tester.

-(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
   ...
}

Étant nouveau dans le développement de l'objectif-c, du cacao et de l'iPhone en général, j'ai un fort désir de tirer le meilleur parti du langage et des frameworks.

Une des ressources que j'utilise est les notes de classe CS193P de Stanford qu'ils ont laissées sur le web. Il comprend des notes de cours, des devoirs et un exemple de code, et comme le cours a été donné par Apple, je considère définitivement que c'est «de la bouche du cheval».

Site Web de la classe:
http://www.stanford.edu/class/cs193p/cgi-bin/index.php

La leçon 08 est liée à une affectation pour construire une application basée sur UINavigationController qui a plusieurs UIViewControllers poussés sur la pile UINavigationController. C'est ainsi que fonctionne UINavigationController. C'est logique. Cependant, il y a quelques avertissements sévères dans la diapositive sur la communication entre vos UIViewControllers.

Je vais citer un extrait de cette série de diapositives:
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf

Page 16/51:

Comment ne pas partager les données

  • Variables globales ou singletons
    • Cela inclut votre délégué d'application
  • Les dépendances directes rendent votre code moins réutilisable
    • Et plus difficile à déboguer et tester

D'accord. Je suis avec ça. Ne jetez pas aveuglément toutes vos méthodes qui seront utilisées pour communiquer entre le viewcontroller dans votre délégué d'application et référencer les instances de viewcontroller dans les méthodes de délégué d'application. Fair 'nuff.

Un peu plus loin, nous obtenons cette diapositive nous indiquant ce que nous devrions faire.

Page 18/51:

Meilleures pratiques pour le flux de données

  • Déterminez exactement ce qui doit être communiqué
  • Définir les paramètres d'entrée pour votre contrôleur de vue
  • Pour communiquer en arrière dans la hiérarchie, utilisez un couplage lâche
    • Définir une interface générique pour les observateurs (comme la délégation)

Cette diapositive est ensuite suivie de ce qui semble être une diapositive où le conférencier démontre alors les meilleures pratiques en utilisant un exemple avec le UIImagePickerController. J'aimerais que les vidéos soient disponibles! :(

Ok, alors ... j'ai peur que mon objc-fu ne soit pas si fort. Je suis aussi un peu confus par la dernière ligne de la citation ci-dessus. J'ai fait ma part de googler à ce sujet et j'ai trouvé ce qui semble être un article décent parlant des différentes méthodes de techniques d'observation / notification:
http://cocoawithlove.com/2008/06/five-approaches-to-listening-observing.html

La méthode n ° 5 indique même les délégués comme une méthode! Sauf que .... les objets ne peuvent définir qu'un seul délégué à la fois. Alors, quand j'ai plusieurs communications avec viewcontroller, que dois-je faire?

Ok, c'est le gang mis en place. Je sais que je peux facilement faire mes méthodes de communication dans le délégué de l'application par référence aux multiples instances viewcontroller dans mon appdelegate mais je veux faire ce genre de chose de la bonne façon.

Aidez-moi s'il vous plaît à "faire ce qu'il faut" en répondant aux questions suivantes:

  1. Quand j'essaye de pousser un nouveau viewcontroller sur la pile UINavigationController, qui devrait faire ce push. Quelle classe / fichier dans mon code est le bon endroit?
  2. Quand je veux affecter un morceau de données (valeur d'un iVar) dans un de mes UIViewControllers quand je suis dans un UIViewController différent , quelle est la "bonne" façon de le faire?
  3. Donner que nous ne pouvons avoir qu'un seul délégué à la fois dans un objet, à quoi ressemblerait la mise en œuvre lorsque le conférencier dit «Définir une interface générique pour les observateurs (comme la délégation)» . Un exemple de pseudocode serait terriblement utile ici si possible.

Ce genre de chose est toujours une question de goût.

Cela dit, je préfère toujours faire ma coordination (# 2) via des objets modèles. Le contrôleur de vue de niveau supérieur charge ou crée les modèles dont il a besoin et chaque contrôleur de vue définit des propriétés dans ses contrôleurs enfants pour leur indiquer les objets de modèle avec lesquels ils doivent travailler. La plupart des modifications sont communiquées dans la hiérarchie en utilisant NSNotificationCenter; Le déclenchement des notifications est généralement intégré au modèle lui-même.

Par exemple, supposons que j'ai une application avec Comptes et Transactions. J'ai aussi un AccountListController, un AccountController (qui affiche un résumé de compte avec un bouton "show all transactions"), un TransactionListController et un TransactionController. AccountListController charge une liste de tous les comptes et les affiche. Lorsque vous appuyez sur un élément de la liste, il définit la propriété .account de son AccountController et pousse le AccountController sur la pile. Lorsque vous appuyez sur le bouton "Afficher toutes les transactions", AccountController charge la liste des transactions, la place dans la propriété .transactions de TransactionListController et la transfère dans la pile, et ainsi de suite.

Si, par exemple, TransactionController édite la transaction, il effectue la modification dans son objet de transaction, puis appelle sa méthode 'save'. 'save' envoie un TransactionChangedNotification. Tout autre contrôleur qui a besoin de se rafraîchir lorsque la transaction change observer la notification et se mettre à jour. TransactionListController serait probablement; AccountController et AccountListController pourraient, selon ce qu'ils essayaient de faire.

Pour # 1, dans mes premières applications, j'ai eu une sorte de displayModel: withNavigationController: méthode dans le contrôleur enfant qui mettrait les choses en place et pousser le contrôleur sur la pile. Mais comme je suis devenu plus à l'aise avec le SDK, je me suis éloigné de cela, et maintenant, j'ai l'habitude d'avoir le parent pousser l'enfant.

Pour # 3, considérez cet exemple. Ici, nous utilisons deux contrôleurs, AmountEditor et TextEditor, pour éditer deux propriétés d'une Transaction. Les éditeurs ne doivent pas enregistrer la transaction en cours de modification, car l'utilisateur peut décider d'abandonner la transaction. Donc, à la place, ils prennent tous les deux leur contrôleur parent en tant que délégué et appellent une méthode pour dire s'ils ont changé quelque chose.

@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

Et maintenant quelques méthodes de 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 chose à noter est que nous avons défini un protocole générique que les éditeurs peuvent utiliser pour communiquer avec leur contrôleur propriétaire. Ce faisant, nous pouvons réutiliser les éditeurs dans une autre partie de l'application. (Peut-être que les comptes peuvent aussi avoir des notes.) Bien entendu, le protocole EditorDelegate pourrait contenir plus d'une méthode; dans ce cas c'est le seul nécessaire.


Supposons qu'il existe deux classes A et B.

instance de la classe A est

Une aInstance;

classe A fait et instance de classe B, comme

B bInstance;

Et dans votre logique de classe B, quelque part vous devez communiquer ou déclencher une méthode de classe A.

1) Mauvaise façon

Vous pouvez passer l'aInstance à bInstance. placez maintenant l'appel de la méthode désirée [aInstance methodname] depuis l'emplacement souhaité dans bInstance.

Cela aurait servi votre but, mais si la libération aurait conduit à un souvenir verrouillé et non libéré.

Comment?

Lorsque vous avez passé la propriété aInstance à bInstance, nous avons augmenté le taux de disponibilité de aInstance de 1. Lorsque vous libérez bInstance, la mémoire est bloquée, car aInstance ne peut jamais être ramené à 0 retincount par la raison bInstance étant que bInstance lui-même est un objet d'unInstance.

De plus, en raison du blocage d'une occurrence, la mémoire de bInstance sera également bloquée (fuite). Ainsi, même après avoir libéré aInstance lui-même quand son heure arrivera plus tard, sa mémoire sera également bloquée car bInstance ne peut pas être libéré et bInstance est une variable de classe de aInstance.

2) Droit chemin

En définissant aInstance en tant que délégué de bInstance, il n'y aura pas de modification retaincount ou d'enchevêtrement de mémoire d'unInstance.

bInstance sera en mesure d'invoquer librement les méthodes déléguées se trouvant dans l'aInstance. Sur la désallocation de bInstance, toutes les variables seront créées et seront libérées sur la désallocation d'uneInstance, car il n'y a pas d'enchevêtrement d'uneInstance dans bInstance, elle sera libérée proprement.


Je vois ton problème ..

Ce qui est arrivé est que quelqu'un a une idée confuse de l'architecture MVC.

MVC a trois parties .. les modèles, les vues, et les contrôleurs .. Le problème déclaré semble avoir combiné deux d'entre eux pour une bonne raison. les vues et les contrôleurs sont des éléments de logique distincts.

donc ... vous ne voulez pas avoir plusieurs view-controllers ..

vous voulez avoir plusieurs vues, et un contrôleur qui choisit entre eux. (vous pouvez également avoir plusieurs contrôleurs, si vous avez plusieurs applications)

les points de vue ne devraient PAS être des décisions. Le contrôleur (s) devrait le faire. D'où la séparation des tâches, la logique et les moyens de vous faciliter la vie.

Alors ... assurez-vous que votre point de vue ne fait que cela, met un bon aperçu des données. laissez votre contrôleur décider quoi faire avec les données et quelle vue utiliser.

(et quand nous parlons de données, nous parlons du modèle ... une manière standard agréable d'être stocké, consulté, modifié ... un autre morceau de logique que nous pouvons oublier et oublier)


Les équivalents sont IB_DESIGNABLE et IBInspectable - voir la documentation pour les exemples Objective C.

En outre, la vidéo de la WWDC de cette année, «Quoi de neuf dans Interface Builder» a ses exemples dans Swift, mais le présentateur mentionne également les équivalents Objective C en passant.





objective-c iphone cocoa-touch delegates key-value-observing