162

Будучи новым объектно-ориентированным, какао и iPhone dev в целом, у меня есть сильное желание максимально использовать язык и рамки.Каков наилучший способ связи между контроллерами?

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

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

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

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

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

Как не обмениваться данными

  • Глобальные переменные или одиночек
    • Включает в себя ваш приложения делегат
  • Прямые зависимости сделать код менее многоразовые
    • И еще труднее отлаживать & тест

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

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

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

Best Practices для потока данных

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

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

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

Способ № 5 даже указывает делегатов как метода исключением .... объекты можно установить только один делегат за один раз. Поэтому, когда у меня есть множественная связь с диспетчером контроля, что мне делать?

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

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

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

Некоторые из этих вопросов рассматриваются в этой статье от Apple - http://developer.apple.com/library/ios/# featuredarticles/ViewControllerPGforiPhoneOS/ManagingDataFlowBetweenViewControllers.html –

+0

Просто быстрое замечание: видео для класса Stanford CS193P теперь доступно через iTunes U. Последний (2012-13) можно увидеть на https://itunes.apple.com/us/course/coding-together-developing/id593208016, и я ожидаю, что будущие видео и слайды будут объявлены по адресу http://cs193p.stanford.edu –

ответ

15

Такая вещь всегда является делом вкуса.

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

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

Если, скажем, TransactionController изменяет транзакцию, она вносит изменения в свой объект транзакции и затем вызывает свой метод «сохранить». «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 может содержать более одного метода; в этом случае это единственное, что необходимо.

+1

Предполагается, что это работает как есть? У меня проблемы с членом 'Editor.delegate'. В моем методе 'viewDidLoad' я получаю делегат' Property '' not found ... '. Я просто не уверен, что я прикрутил что-то еще. Или если это сокращено для краткости. – Jeff

+0

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

+0

Gotcha. Это именно то, что я хотел знать. Я получил его с некоторыми изменениями, но я был немного обеспокоен тем, что он не подходит дословно. – Jeff

0

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

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

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

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

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

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

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

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

223

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

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

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

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

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

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

Пример использования Dependency Injection с View Controller

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

Чтобы создать это, вы можете создать класс BookPickerViewController, который контролирует и отображает объекты GUI/view. Где он получит все данные книги? Скажем, это зависит от объекта BookWarehouse. Итак, теперь ваш контроллер в основном осуществляет обмен данными между объектами модели (BookWarehouse) и объектами графического интерфейса/представления. Другими словами, BookPickerViewController DEPENDS на объекте 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 после того, как пользователь выбрал свои книги и готов проверить? Ну, на самом деле это не его работа. Он должен DELEGATE, чтобы работать с каким-то другим объектом, а это означает, что он ДЕПРЕССИРУЕТСЯ на другом объекте. Таким образом, мы могли бы изменить наше 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 
    ... 
} 
+19

Когда я вижу вопросы (и ответы) вроде этого, созданные с такой осторожностью, я не могу не улыбнуться. Хорошо заслуженное признание нашему бесстрашному собеседнику и вам! Тем временем я хотел поделиться обновленной ссылкой на эту удобную ссылку invasivecode.com, на которую вы ссылались во втором пункте: http://www.invasivecode.com/2009/09/implementing-a-navigation-controller-uinavigationcontroller-with-interface -builder/- Еще раз спасибо за то, что вы делитесь своими идеями и лучшими практиками, а также поддерживаете их примерами! –

+0

Согласен. Вопрос был хорошо сформирован, и ответ был просто фантастическим. Вместо того, чтобы просто иметь технический ответ, он также включал некоторую психологию за тем, как/почему он реализован с использованием DI. Спасибо! +1 вверх. –

+0

Что делать, если вы также хотите использовать BookPickerController для выбора книги для списка пожеланий или одной из нескольких возможных причин написания книги. Вы по-прежнему используете подход интерфейса CheckoutController (возможно, переименованный в нечто вроде BookSelectionController) или, возможно, используете NSNotificationCenter? – Les

0

Предположим, что существует два класса А и В.

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

aInstance;

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

B bInstance;

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

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

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

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

Как?

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

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

2) Правильный способ

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

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

Смежные вопросы