Będąc nowicjuszem w zakresie programowania Objective-C, Cocoa i iPhone'a w ogóle, pragnę jak najlepiej wykorzystać język i ramy.
Jednym z zasobów, z których korzystam, są notatki z klasy CS193P Stanforda, które pozostawili w Internecie. Zawiera notatki do wykładów, zadania i przykładowy kod, a ponieważ kurs został poprowadzony przez programistów Apple, zdecydowanie uważam, że jest on „z końskiej paszczy”.
Strona internetowa klasy:
http://www.stanford.edu/class/cs193p/cgi-bin/index.php
Wykład 08 jest powiązany z zadaniem zbudowania aplikacji opartej na UINavigationController, która ma wiele UIViewControllers wypychanych na stos UINavigationController. Tak działa UINavigationController. To logiczne. Jednak na slajdzie znajdują się poważne ostrzeżenia dotyczące komunikacji między kontrolerami UIViewControllers.
Cytuję z tego poważnego slajdu:
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf
Strona 16/51:
Jak nie udostępniać danych
- Zmienne globalne lub pojedyncze
- Obejmuje to delegata aplikacji
- Bezpośrednie zależności sprawiają, że kod jest trudniejszy do ponownego wykorzystania
- I trudniejsze do debugowania i testowania
Dobrze. Nie mam tego. Nie wrzucaj na ślepo wszystkich metod, które będą używane do komunikacji między kontrolerem widoku do delegata aplikacji i odwołuj się do wystąpień viewcontroller w metodach delegata aplikacji. Fair 'nuff.
Nieco dalej otrzymujemy ten slajd, który mówi nam, co powinniśmy zrobić.
Strona 18/51:
Najlepsze praktyki dotyczące przepływu danych
- Dowiedz się dokładnie, co należy przekazać
- Zdefiniuj parametry wejściowe dla kontrolera widoku
- Do komunikacji w górę hierarchii użyj luźnego połączenia
- Zdefiniuj ogólny interfejs dla obserwatorów (np. Delegowanie)
Po tym slajdzie następuje slajd, który wydaje się być slajdem zastępczym, na którym wykładowca następnie najwyraźniej demonstruje najlepsze praktyki na przykładzie z UIImagePickerController. Chciałbym, żeby filmy były dostępne! :(
Ok, więc ... Obawiam się, że moje objc-fu nie jest tak mocne. Jestem też trochę zdezorientowany ostatnią linią powyższego cytatu. Sporo robiłem w Google na ten temat i znalazłem przyzwoity artykuł opisujący różne metody technik obserwacji / powiadamiania:
http://cocoawithlove.com/2008/06/five-approaches-to -listening-observing.html
Metoda nr 5 wskazuje nawet delegatów jako metodę! Z wyjątkiem obiektów .... można ustawić tylko jednego delegata naraz. Więc kiedy mam komunikację z wieloma kontrolerami widoku, co mam zrobić?
Ok, to gang z ustawieniami. Wiem, że mogę łatwo wykonywać moje metody komunikacji w delegacie aplikacji, korzystając z wielu wystąpień kontrolera widoku w moim appdelegate, ale chcę to zrobić we właściwy sposób.
Pomóż mi „postępować właściwie”, odpowiadając na następujące pytania:
- Kiedy próbuję wypchnąć nowy kontroler widoku na stosie UINavigationController, kto powinien wykonywać to wypychanie. Która klasa / plik w moim kodzie jest właściwym miejscem?
- Kiedy chcę wpłynąć na niektóre dane (wartość iVar) w jednym z moich kontrolerów UIViewControllers, gdy jestem w innym UIViewController, jaki jest „właściwy” sposób, aby to zrobić?
- Daj nam tylko jednego delegata ustawionego w danym momencie w obiekcie, jak wyglądałaby implementacja, gdy prowadzący powie: „Zdefiniuj ogólny interfejs dla obserwatorów (np. Delegacja)” . Przykład pseudokodu byłby tutaj bardzo pomocny, jeśli to możliwe.
źródło
Odpowiedzi:
To są dobre pytania i wspaniale jest widzieć, że robisz te badania i wydaje się, że zależy Ci na tym, aby nauczyć się „robić to dobrze”, zamiast po prostu je razem łamać.
Po pierwsze , zgadzam się z poprzednimi odpowiedziami, które koncentrują się na znaczeniu umieszczania danych w obiektach modelu, gdy jest to stosowne (zgodnie ze wzorcem projektowym MVC). Zwykle chcesz uniknąć umieszczania informacji o stanie wewnątrz kontrolera, chyba że są to dane ściśle „prezentacyjne”.
Po drugie , na stronie 10 prezentacji Stanforda znajduje się przykład programowego wciskania kontrolera na kontroler nawigacyjny. Aby zapoznać się z przykładem, jak to zrobić „wizualnie” przy użyciu programu Interface Builder, zapoznaj się z tym samouczkiem .
Po trzecie , i być może najważniejsze, zwróć uwagę, że „najlepsze praktyki” wspomniane w prezentacji na Uniwersytecie Stanforda są znacznie łatwiejsze do zrozumienia, jeśli myślisz o nich w kontekście wzorca projektowego „wstrzykiwania zależności”. W skrócie oznacza to, że kontroler nie powinien „wyszukiwać” obiektów, których potrzebuje do wykonania swojej pracy (np. Odwoływać się do zmiennej globalnej). Zamiast tego należy zawsze „wstrzykiwać” te zależności do kontrolera (tj. Przekazywać potrzebne obiekty za pomocą metod).
Jeśli zastosujesz się do wzorca iniekcji zależności, twój kontroler będzie modułowy i wielokrotnego użytku. A jeśli pomyślisz o tym, skąd pochodzą prezenterzy ze Stanford (np. Ich zadaniem jako pracowników Apple jest tworzenie klas, które można łatwo ponownie wykorzystać), ponowne wykorzystanie i modułowość są priorytetami. Wszystkie najlepsze praktyki, o których wspominają w zakresie udostępniania danych, są częścią wstrzykiwania zależności.
To istota mojej odpowiedzi. Poniżej zamieszczę przykład użycia wzorca iniekcji zależności z kontrolerem na wypadek, gdyby był pomocny.
Przykład użycia iniekcji zależności z kontrolerem widoku
Załóżmy, że tworzysz ekran, na którym znajduje się kilka książek. Użytkownik może wybrać książki, które chce kupić, a następnie dotknąć przycisku „Do kasy”, aby przejść do ekranu kasy.
Aby to zbudować, możesz utworzyć klasę BookPickerViewController, która kontroluje i wyświetla obiekty GUI / widoku. Skąd weźmie wszystkie dane książki? Powiedzmy, że zależy to od obiektu BookWarehouse. Więc teraz twój kontroler po prostu pośredniczy w przesyłaniu danych między obiektem modelu (BookWarehouse) a GUI / obiektami widoku. Innymi słowy, BookPickerViewController ZALEŻY od obiektu BookWarehouse.
Nie rób tego:
Zamiast tego zależności należy wstrzyknąć w następujący sposób:
Kiedy faceci z Apple mówią o wykorzystaniu wzorca delegowania do „komunikacji w górę hierarchii”, wciąż mówią o wstrzykiwaniu zależności. W tym przykładzie, co powinien zrobić BookPickerViewController, gdy użytkownik wybrał swoje książki i jest gotowy do pobrania? Cóż, to naprawdę nie jest jego praca. Powinien DELEGOWAĆ to działanie do innego obiektu, co oznacza, że ZALEŻY to od innego obiektu. Więc możemy zmodyfikować naszą metodę inicjującą BookPickerViewController w następujący sposób:
Rezultatem tego wszystkiego jest to, że możesz dać mi swoją klasę BookPickerViewController (i powiązane obiekty GUI / widoku) i mogę z łatwością użyć jej w mojej własnej aplikacji, zakładając, że BookWarehouse i CheckoutController to ogólne interfejsy (tj. Protokoły), które mogę zaimplementować :
Wreszcie, Twój BookPickerController jest nie tylko wielokrotnego użytku, ale także łatwiejszy do przetestowania.
źródło
Takie rzeczy zawsze są kwestią gustu.
Powiedziawszy to, zawsze wolę koordynować (# 2) za pomocą obiektów modelu. Kontroler widoku najwyższego poziomu ładuje lub tworzy modele, których potrzebuje, a każdy kontroler widoku ustawia właściwości w swoich kontrolerach podrzędnych, aby powiedzieć im, z którymi obiektami modelu muszą pracować. Większość zmian jest przekazywana z powrotem w hierarchii za pomocą NSNotificationCenter; wypalanie powiadomień jest zwykle wbudowane w sam model.
Załóżmy na przykład, że mam aplikację z kontami i transakcjami. Mam również AccountListController, AccountController (który wyświetla podsumowanie konta z przyciskiem „pokaż wszystkie transakcje”), TransactionListController i TransactionController. AccountListController ładuje listę wszystkich kont i wyświetla je. Po dotknięciu elementu listy ustawia on właściwość .account jego AccountController i wypycha AccountController na stos. Po naciśnięciu przycisku „pokaż wszystkie transakcje”, AccountController ładuje listę transakcji, umieszcza ją we właściwości .transactionsListControllera TransactionList i umieszcza TransactionListController na stosie i tak dalej.
Jeśli, powiedzmy, TransactionController edytuje transakcję, dokonuje zmiany w swoim obiekcie transakcji, a następnie wywołuje swoją metodę „save”. „Zapisz” wysyła TransactionChangedNotification. Każdy inny kontroler, który musi się odświeżyć, gdy transakcja się zmienia, obserwowałby powiadomienie i aktualizował się. Prawdopodobnie TransactionListController; AccountController i AccountListController mogą, w zależności od tego, co próbowały zrobić.
W przypadku # 1 w moich wczesnych aplikacjach miałem pewien rodzaj displayModel: withNavigationController: metoda w kontrolerze podrzędnym, która ustawiałaby rzeczy i umieszczała kontroler na stosie. Ale kiedy poczułem się bardziej komfortowo z SDK, odpłynąłem od tego i teraz zazwyczaj rodzic popycha dziecko.
W przypadku # 3 rozważ ten przykład. Tutaj używamy dwóch kontrolerów, AmountEditor i TextEditor, aby edytować dwie właściwości transakcji. Redaktorzy nie powinni faktycznie zapisywać edytowanej transakcji, ponieważ użytkownik może zdecydować o rezygnacji z transakcji. Zamiast tego oboje przyjmują swój kontroler nadrzędny jako delegata i wywołują na nim metodę, mówiąc, czy cokolwiek zmienili.
A teraz kilka metod z TransactionController:
Należy zauważyć, że zdefiniowaliśmy ogólny protokół, którego redaktorzy mogą używać do komunikowania się ze swoim kontrolerem. W ten sposób możemy ponownie wykorzystać Edytorów w innej części aplikacji. (Być może konta też mogą zawierać notatki). Oczywiście protokół EditorDelegate może zawierać więcej niż jedną metodę; w tym przypadku jest to jedyne konieczne.
źródło
Editor.delegate
członkiem. W mojejviewDidLoad
metodzie dostajęProperty 'delegate' not found...
. Po prostu nie jestem pewien, czy schrzaniłem coś innego. Lub jeśli jest to skrócone dla zwięzłości.Widzę twój problem ...
Stało się tak, że ktoś pomylił pojęcie architektury MVC.
MVC składa się z trzech części… modeli, widoków i kontrolerów… Podany problem wydaje się być połączeniem dwóch z nich bez powodu. widoki i kontrolery są oddzielnymi elementami logiki.
więc ... nie chcesz mieć wielu kontrolerów widoku ...
chcesz mieć wiele widoków i kontroler, który wybiera między nimi. (możesz również mieć wiele kontrolerów, jeśli masz wiele aplikacji)
poglądy NIE powinny wpływać na podejmowanie decyzji. Powinni to zrobić kontroler (y). Stąd rozdział zadań, logiki i sposobów ułatwienia sobie życia.
Więc ... upewnij się, że twój widok po prostu to robi, wyświetla ładny widok danych. pozwól administratorowi zdecydować, co zrobić z danymi iz jakiego widoku skorzystać.
(a kiedy mówimy o danych, mówimy o modelu ... fajny standardowy sposób przechowywania, uzyskiwania dostępu, modyfikowania ... kolejny oddzielny element logiki, który możemy spakować i zapomnieć)
źródło
Załóżmy, że istnieją dwie klasy A i B.
wystąpienie klasy A to
A aInstance;
klasa A marki i egzemplarze klasy B, as
B bInstance;
Zgodnie z logiką klasy B, gdzieś jesteś zobowiązany do komunikowania się lub wyzwalania metody klasy A.
1) Zły sposób
Możesz przekazać aInstance do bInstance. teraz umieść wywołanie żądanej metody [aInstance nazwa metody] z żądanej lokalizacji w bInstance.
To by służyło twojemu celowi, ale podczas gdy uwolnienie doprowadziłoby do zablokowania pamięci, a nie uwolnienia.
W jaki sposób?
Kiedy przekazałeś aInstance do bInstance, zwiększyliśmy retaincount of aInstance o 1. Podczas zwalniania bInstance będziemy mieli zablokowaną pamięć, ponieważ aInstance nigdy nie może zostać sprowadzona do 0 retaincount przez bInstance, ponieważ bInstance jest obiektem aInstance.
Ponadto, z powodu zablokowania aInstance, pamięć bInstance również zostanie zablokowana (wyciekła). Więc nawet po cofnięciu alokacji samego aInstance, gdy nadejdzie później jego czas, jego pamięć również zostanie zablokowana, ponieważ bInstance nie może zostać zwolniony, a bInstance jest zmienną klasy aInstance.
2) Właściwy sposób
Definiując aInstance jako delegata bInstance, nie będzie żadnej zmiany retaincount ani splątania pamięci aInstance.
bInstance będzie mógł swobodnie wywoływać metody delegatów znajdujące się w aInstance. Po zwolnieniu alokacji bInstance wszystkie zmienne zostaną utworzone samodzielnie i zostaną zwolnione. W przypadku zwolnienia aInstance, ponieważ nie ma splątania aInstance w bInstance, zostaną one zwolnione w sposób czysty.
źródło