Каков наилучший способ связи между контроллерами представления? - PullRequest
164 голосов
/ 20 февраля 2009

Будучи новичком в target-c, какао и iPhone-устройстве в целом, я очень хочу максимально использовать язык и фреймворки.

Одним из ресурсов, которые я использую, являются примечания класса Стэнфорда CS193P, которые они оставили в Интернете. Он включает в себя примечания к лекциям, задания и пример кода, и, поскольку курс читался разработчиками Apple, я определенно считаю его «изо рта лошади».

Класс Сайт:
http://www.stanford.edu/class/cs193p/cgi-bin/index.php

Лекция 08 связана с заданием на создание приложения на основе UINavigationController, в котором несколько стилей UIViewController помещены в стек UINavigationController. Вот как работает UINavigationController. Это логично Однако на слайде есть несколько строгих предупреждений о связи между вашими UIViewControllers.

Я приведу цитату из этого серьезного слайда:
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf

Страница 16/51:

Как не делиться данными

  • Глобальные переменные или синглтоны
    • Сюда входит ваш делегат приложения
  • Прямые зависимости делают ваш код менее пригодным для повторного использования
    • И более сложный для отладки и тестирования

Ok. Я покончил с этим. Не бросайте вслепую все ваши методы, которые будут использоваться для связи между viewcontroller, в ваш делегат приложения и ссылайтесь на экземпляры viewcontroller в методах делегата приложения. Удовлетворительно.

Чуть дальше, мы получаем этот слайд, рассказывающий нам, что мы должны делать.

Стр. 18/51:

Лучшие практики для потока данных

  • Выяснить точно что нужно сообщить
  • Определите входные параметры для контроллера вида
  • Для связи по иерархии, используйте слабую связь
    • Определить общий интерфейс для наблюдателей (например, делегирование)

Затем за этим слайдом следует то, что выглядит как слайд заполнителя, на котором лектор, по-видимому, демонстрирует лучшие практики, используя пример с UIImagePickerController. Я хотел бы, чтобы видео были доступны! (

Хорошо, так ... Боюсь, мой объект не такой сильный. Я также немного смущен последней строкой в ​​приведенной выше цитате. Я делал свою долю в поисках по этому поводу, и я нашел кое-что, что кажется приличной статьей, рассказывающей о различных методах наблюдения / уведомления:
http://cocoawithlove.com/2008/06/five-approaches-to-listening-observing.html

Метод № 5 даже указывает делегатов как метод! За исключением .... объекты могут устанавливать только одного делегата за раз. Итак, что мне делать, если у меня несколько представлений viewcontroller?

Хорошо, это настроенная банда. Я знаю, что могу легко использовать свои методы связи в делегате приложения, ссылаясь на множественные экземпляры viewcontroller в моем appdelegate, но я хочу делать подобные вещи правильным способом.

Пожалуйста, помогите мне "поступить правильно", ответив на следующие вопросы:

  1. Когда я пытаюсь вставить новый viewcontroller в стек UINavigationController, кто должен делать это. Какой класс / файл в моем коде является правильным местом?
  2. Когда я хочу повлиять на какой-то фрагмент данных (значение iVar) в одном из моих контроллеров UIViewController, когда я использую другой UIViewController, каков «правильный» способ сделать это?
  3. Предположим, что в объекте может быть установлен только один делегат за раз, как будет выглядеть реализация, когда лектор говорит «Определить общий интерфейс для наблюдателей (например, делегирование)» . Пример псевдокода был бы здесь очень полезен, если это возможно.

Ответы [ 4 ]

224 голосов
/ 22 февраля 2009

Это хорошие вопросы, и приятно видеть, что вы проводите это исследование и, похоже, заинтересованы в том, чтобы научиться «делать это правильно», а не просто взламывать его вместе.

Первый . Я согласен с предыдущими ответами, в которых основное внимание уделяется важности помещения данных в объекты модели, когда это необходимо (согласно шаблону проектирования MVC). Обычно вы хотите избежать размещения информации о состоянии внутри контроллера, если это не строго данные представления.

Секунда , см. Стр. 10 презентации Стэнфорда для примера того, как программно вставить контроллер в контроллер навигации. Для примера того, как сделать это «визуально» с помощью Interface Builder, взгляните на это руководство .

Третий и, возможно, самое важное, отметьте, что "лучшие практики", упомянутые в презентации Стэнфорда, намного легче понять, если вы думаете о них в контексте " Внедрение зависимости "шаблон проектирования. Короче говоря, это означает, что ваш контроллер не должен «искать» объекты, необходимые для его работы (например, ссылаться на глобальную переменную). Вместо этого вы всегда должны «вводить» эти зависимости в контроллер (т.е. передавать нужные ему объекты с помощью методов).

Если вы следуете шаблону внедрения зависимостей, ваш контроллер будет модульным и может использоваться повторно. И если вы думаете о том, откуда берутся докладчики из Стэнфорда (то есть, когда сотрудники Apple занимаются созданием классов, которые можно легко использовать повторно), многократное использование и модульность являются первоочередными задачами. Все лучшие практики, которые они упоминают для обмена данными, являются частью внедрения зависимости.

Это суть моего ответа. Ниже приведен пример использования шаблона внедрения зависимостей с контроллером на случай, если это будет полезно.

Пример использования внедрения зависимости с контроллером представления

Допустим, вы создаете экран, на котором перечислены несколько книг. Пользователь может выбрать книги, которые он / она хочет купить, а затем нажать кнопку «Оформить заказ», чтобы перейти на экран оформления заказа.

Чтобы построить это, вы можете создать класс BookPickerViewController, который управляет и отображает объекты GUI / представления. Где он получит все данные книги? Допустим, это зависит от объекта BookWarehouse. Итак, теперь ваш контроллер в основном выполняет посреднические функции между объектом модели (BookWarehouse) и объектами GUI / представления. Другими словами, BookPickerViewController ЗАВИСИТ от объекта BookWarehouse.

Не делай этого:

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

Вместо этого зависимости должны быть введены следующим образом:

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

Когда парни из Apple говорят об использовании шаблона делегирования для «обратной связи по иерархии», они все еще говорят о внедрении зависимости. В этом примере, что должен делать BookPickerViewController, как только пользователь выбрал свои книги и готов их проверить? Ну, это не совсем его работа. Он должен ДЕЛЕГАТИЗИРОВАТЬ эту работу с другим объектом, что означает, что он ЗАВИСИТ от другого объекта. Поэтому мы можем изменить наш метод инициализации BookPickerViewController следующим образом:

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

Итогом всего этого является то, что вы можете дать мне свой класс BookPickerViewController (и связанные объекты GUI / представления), и я могу легко использовать его в своем собственном приложении, предполагая, что BookWarehouse и CheckoutController являются общими интерфейсами (т.е. протоколами), Я могу реализовать:

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

Наконец, ваш BookPickerController не только можно использовать повторно, но и проще в тестировании.

-(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
   ...
}
15 голосов
/ 22 февраля 2009

Подобные вещи - дело вкуса.

Сказав это, я всегда предпочитаю выполнять координацию (# 2) с помощью модельных объектов. Контроллер представления верхнего уровня загружает или создает необходимые модели, и каждый контроллер представления устанавливает свойства в своих дочерних контроллерах, чтобы сообщить им, с какими объектами модели они должны работать. Большинство изменений передаются обратно в иерархию с помощью NSNotificationCenter; запуск уведомлений обычно встроен в саму модель.

Например, предположим, у меня есть приложение со счетами и транзакциями. У меня также есть AccountListController, AccountController (который отображает сводку учетной записи с помощью кнопки «показать все транзакции»), TransactionListController и TransactionController. AccountListController загружает список всех учетных записей и отображает их. Когда вы нажимаете на элемент списка, он устанавливает свойство .account своего AccountController и помещает AccountController в стек. Когда вы нажимаете кнопку «показать все транзакции», AccountController загружает список транзакций, помещает его в свойство .transactions своего TransactionListController и помещает TransactionListController в стек и т. Д.

Если, например, TransactionController редактирует транзакцию, он вносит изменения в свой объект транзакции, а затем вызывает его метод 'save'. «save» отправляет TransactionChangedNotification. Любой другой контроллер, который должен обновлять себя при изменении транзакции, будет наблюдать уведомление и обновлять себя. TransactionListController предположительно будет; AccountController и AccountListController могут, в зависимости от того, что они пытались сделать.

Для # 1, в моих ранних приложениях у меня был своего рода displayModel: withNavigationController: метод в дочернем контроллере, который настраивал бы вещи и помещал контроллер в стек. Но так как мне стало удобнее с SDK, я отошел от этого, и теперь я обычно заставляю родителей подталкивать ребенка.

Для # 3, рассмотрим этот пример. Здесь мы используем два контроллера, AmountEditor и TextEditor, чтобы редактировать два свойства транзакции. Редакторы не должны на самом деле сохранять редактируемую транзакцию, поскольку пользователь может решить отказаться от транзакции. Поэтому вместо этого они оба принимают родительский контроллер в качестве делегата и вызывают для него метод, сообщающий, изменили ли они что-либо.

@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

А теперь несколько методов из 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];
}

Следует отметить, что мы определили общий протокол, который редакторы могут использовать для связи со своим контроллером-владельцем. Таким образом, мы можем повторно использовать редакторы в другой части приложения. (Возможно, учетные записи также могут иметь примечания.) Конечно, протокол EditorDelegate может содержать более одного метода; в этом случае это единственное, что необходимо.

0 голосов
/ 05 марта 2012

Предположим, есть два класса A и B.

экземпляр класса A равен

A aInstance;

класс A делает и экземпляр класса B, как

B bInstance;

И в вашей логике класса B, где-то вы обязаны сообщать или вызывать метод класса A.

1) Неправильный путь

Вы можете передать экземпляр в экземпляр. Теперь поместите вызов нужного метода [имя метода aInstance] из нужного места в bInstance.

Это бы послужило вашей цели, но в то время как освобождение привело бы к блокировке памяти и не освобождению.

Как?

Когда вы передали aInstance bInstance, мы увеличили резервный счет aInstance на 1. При освобождении bInstance у нас будет заблокирована память, потому что aInstance никогда не может быть доведен до 0 для сохранения счета по причине bInstance, заключающейся в том, что сам bInstance является объектом aInstance.

Кроме того, из-за зависания экземпляра память bInstance также будет зависать (вытекать). Таким образом, даже после освобождения самого aInstance, когда наступит его время, его память также будет заблокирована, поскольку bInstance не может быть освобожден, а bInstance является переменной класса aInstance.

2) Правильный путь

При определении aInstance в качестве делегата bInstance не будет никакого изменения сохраняемого счета или запутывания памяти в aInstance.

bInstance сможет свободно вызывать методы делегата, лежащие в aInstance. При освобождении bInstance все переменные будут созданы самостоятельно и будут освобождены При освобождении aInstance, поскольку в bInstance нет запутывания aInstance, он будет освобожден без ошибок.

0 голосов
/ 22 февраля 2009

Я вижу вашу проблему ..

Случилось так, что кто-то запутал идею архитектуры MVC.

MVC состоит из трех частей .. моделей, представлений и контроллеров. Кажется, заявленная проблема объединила две из них без веской причины. представления и контроллеры - это отдельные кусочки логики.

так что ... вы не хотите иметь несколько контроллеров представления ..

вы хотите иметь несколько представлений и контроллер, который выбирает между ними. (у вас также может быть несколько контроллеров, если у вас несколько приложений)

взгляды НЕ должны принимать решения. Контроллер (ы) должны сделать это. Отсюда разделение задач, логика и способы облегчения вашей жизни.

Итак ... убедитесь, что ваш взгляд именно так и поступает, выдает хороший вид данных. пусть ваш контроллер решит, что делать с данными и какое представление использовать.

(а когда мы говорим о данных, мы говорим о модели ... хороший стандартный способ хранения, доступа, изменения ... еще один отдельный фрагмент логики, который мы можем отделить и забыть)

...