it-roy-ru.com

Каков наилучший способ связи между контроллерами представления?

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

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

Сайт класса:
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:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Сначала , я согласен с предыдущими ответами, которые фокусируются на важности помещения данных в объекты модели, когда соответствующий (согласно схеме проектирования 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
   ...
}
224
Clint Harris

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

Сказав это, я всегда предпочитаю выполнять координацию (# 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, я отошел от этого, и теперь у меня обычно есть родитель Push ребенка.

Для # 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 может содержать более одного метода; в этом случае это единственное, что необходимо.

15
Brent Royal-Gordon

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

экземпляр класса А является

Экземпляр;

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

B bInstance;

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

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

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

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

Как?

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

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

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

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

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

0
rd_

Я вижу твою проблему ..

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

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

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

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

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

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

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

0
Bingy