Mam problem z implementacją wzorca MVC na iOS. Przeszukałem Internet, ale wydaje się, że nie znalazłem żadnego fajnego rozwiązania tego problemu.
Wiele UITableViewController
wdrożeń wydaje się być dość dużych. Większość przykładów, które widziałem, pozwala na UITableViewController
wdrożenie <UITableViewDelegate>
i <UITableViewDataSource>
. Te wdrożenia są dużym powodem, dla którego UITableViewController
robi się duże. Jednym rozwiązaniem byłoby stworzenie osobnych klas, które implementują <UITableViewDelegate>
i <UITableViewDataSource>
. Oczywiście klasy te musiałyby mieć odniesienie do UITableViewController
. Czy korzystanie z tego rozwiązania ma jakieś wady? Ogólnie myślę, że powinieneś przekazać tę funkcjonalność innym klasom „pomocników” lub podobnym, korzystając ze wzoru delegowania. Czy istnieją jakieś dobrze ustalone sposoby rozwiązania tego problemu?
Nie chcę, aby model zawierał zbyt wiele funkcji ani widoku. Uważam, że logika powinna naprawdę należeć do klasy kontrolerów, ponieważ jest to jeden z fundamentów wzorca MVC. Ale najważniejsze pytanie brzmi:
Jak podzielić kontroler implementacji MVC na mniejsze części, którymi można zarządzać? (W tym przypadku dotyczy MVC w iOS)
Być może istnieje ogólny wzorzec rozwiązania tego problemu, chociaż konkretnie szukam rozwiązania dla systemu iOS. Podaj przykład dobrego wzoru rozwiązania tego problemu. Podaj argument, dlaczego Twoje rozwiązanie jest niesamowite.
UITableViewController
mechanika wydaje mi się dość dziwna, więc mogę odnieść się do problemu. Jestem naprawdę zadowolony, że stosowanieMonoTouch
, ponieważMonoTouch.Dialog
specjalnie robi to że dużo łatwiej jest pracować z tabelami na iOS. Tymczasem jestem ciekawy, co mogą zasugerować tu inni, bardziej kompetentni ludzie ...Odpowiedzi:
Unikam używania
UITableViewController
, ponieważ nakłada wiele obowiązków na pojedynczy obiekt. Dlatego oddzielamUIViewController
podklasę od źródła danych i deleguję. Obowiązkiem kontrolera widoku jest przygotowanie widoku tabeli, utworzenie źródła danych z danymi i połączenie tych elementów. Zmiana sposobu reprezentacji widoku tabeli może być wykonana bez zmiany kontrolera widoku, a w rzeczywistości ten sam kontroler widoku może być używany dla wielu źródeł danych, które wszystkie stosują ten wzorzec. Podobnie zmiana przepływu pracy aplikacji oznacza zmiany w kontrolerze widoku bez obawy o to, co stanie się z tabelą.Próbowałem rozdzielić protokoły
UITableViewDataSource
iUITableViewDelegate
na różne obiekty, ale zwykle kończy się to fałszywym podziałem, ponieważ prawie każda metoda na uczestniku musi wkopać się w źródło danych (np. Po wybraniu uczestnik musi wiedzieć, który obiekt jest reprezentowany przez wybrany wiersz). W rezultacie otrzymuję pojedynczy obiekt, który jest zarówno źródłem danych, jak i delegatem. Ten obiekt zawsze zapewnia metodę, w-(id)tableView: (UITableView *)tableView representedObjectAtIndexPath: (NSIndexPath *)indexPath
której zarówno źródło danych, jak i delegat muszą wiedzieć, nad czym pracują.To moja separacja problemów na „poziomie 0”. Poziom 1 angażuje się, jeśli muszę reprezentować różnego rodzaju obiekty w tym samym widoku tabeli. Na przykład wyobraź sobie, że musiałeś napisać aplikację Kontakty - dla jednego kontaktu możesz mieć wiersze reprezentujące numery telefonów, inne wiersze reprezentujące adresy, inne reprezentujące adresy e-mail i tak dalej. Chcę uniknąć tego podejścia:
Jak dotąd zaprezentowano dwa rozwiązania. Jednym z nich jest dynamiczne skonstruowanie selektora:
W tym podejściu nie trzeba edytować epickiego
if()
drzewa, aby obsługiwać nowy typ - wystarczy dodać metodę, która obsługuje nową klasę. Jest to świetne podejście, jeśli ten widok tabeli jest jedynym, który musi reprezentować te obiekty lub prezentować je w specjalny sposób. Jeśli te same obiekty będą reprezentowane w różnych tabelach z różnymi źródłami danych, to podejście załamuje się, ponieważ metody tworzenia komórek wymagają współużytkowania przez źródła danych - możesz zdefiniować wspólną nadklasę, która udostępnia te metody, lub możesz to zrobić:Następnie w klasie źródła danych:
Oznacza to, że każde źródło danych, które musi wyświetlać numery telefonów, adresy itp., Może po prostu zapytać o dowolny obiekt reprezentowany dla komórki widoku tabeli. Samo źródło danych nie musi już nic wiedzieć o wyświetlanym obiekcie.
„Ale czekaj”, słyszę hipotetyczny wtrącający się rozmówca, „czy to nie łamie MVC? Czy nie umieszczasz szczegółów widoku w klasie modelki?”
Nie, to nie psuje MVC. W tym przypadku kategorie można traktować jako implementację programu Decorator ; podobnie
PhoneNumber
jak klasa modelu, alePhoneNumber(TableViewRepresentation)
jest kategorią widoku. Źródło danych (obiekt kontrolera) pośredniczy między modelem a widokiem, więc architektura MVC nadal obowiązuje.To wykorzystanie kategorii można również postrzegać jako dekorację w ramach Apple.
NSAttributedString
jest klasą modelową, zawierającą tekst i atrybuty. AppKit zapewnia,NSAttributedString(AppKitAdditions)
a UIKit zapewniaNSAttributedString(NSStringDrawing)
kategorie dekoratorów, które dodają zachowanie rysunkowe do tych klas modeli.źródło
cellForPhotoAtIndexPath
metodzie źródła danych, a następnie wywołano odpowiednią metodę fabryczną. Co oczywiście jest możliwe tylko wtedy, gdy określone klasy mogą zajmować określone wiersze. Twój system generowania widoków kategorii na modelach jest chyba bardziej elegancki w praktyce, choć może to niekonwencjonalne podejście do MVC! :)Ludzie często pakują się dużo w UIViewController / UITableViewController.
Delegowanie do innej klasy (nie kontrolera widoku) zwykle działa dobrze. Delegaci niekoniecznie potrzebują odwołania z powrotem do kontrolera widoku, ponieważ wszystkie metody delegowania otrzymują odniesienie do
UITableView
, ale będą potrzebować dostępu do danych, dla których delegują.Kilka pomysłów na reorganizację w celu skrócenia długości:
jeśli konstruujesz komórki widoku tabeli w kodzie, rozważ załadowanie ich zamiast z pliku stalówki lub ze scenorysu. Scenorysy zezwalają na prototypowe i statyczne komórki tabeli - sprawdź te funkcje, jeśli nie jesteś zaznajomiony
jeśli twoje metody delegowania zawierają wiele instrukcji „if” (lub instrukcji switch), jest to klasyczny znak, że możesz dokonać refaktoryzacji
Zawsze wydawało mi się trochę zabawne, że to on
UITableViewDataSource
był odpowiedzialny za uzyskanie poprawnego kawałka danych i skonfigurowanie widoku, aby to pokazać. Jednym fajnym punktem refaktoryzacji może być zmiana danych wcellForRowAtIndexPath
celu uzyskania uchwytu na dane, które wymagają wyświetlenia w komórce, a następnie przekazanie utworzenia widoku komórki innemu delegatowi (np. WykonanieCellViewDelegate
lub podobnym), który zostanie przekazany w odpowiednim elemencie danych.źródło
Oto mniej więcej to, co obecnie robię, gdy napotykam podobny problem:
Przenieś operacje związane z danymi do klasy XXXDataSource (która dziedziczy z BaseDataSource: NSObject). BaseDataSource zapewnia kilka wygodnych metod, takich jak
- (NSUInteger)rowsInSection:(NSUInteger)sectionNum;
podklasa zastępująca metodę ładowania danych (ponieważ aplikacje zwykle mają jakąś metodę ładowania pamięci podręcznej typu offlie,- (void)loadDataWithUpdateBlock:(LoadProgressBlock)dataLoadBlock completion:(LoadCompletionBlock)completionBlock;
tak, abyśmy mogli aktualizować interfejs użytkownika danymi buforowanymi odebranymi w LoadProgressBlock podczas aktualizacji informacji z sieci i w bloku zakończenia odświeżamy interfejs użytkownika nowymi danymi i usuwamy ewentualne wskaźniki postępu). Klasy te NIE są zgodne zUITableViewDataSource
protokołem.W BaseTableViewController (który jest zgodny z
UITableViewDataSource
iUITableViewDelegate
protokoły) Mam odniesienie do BaseDataSource, które tworzą w regulator init. WUITableViewDataSource
części kontrolera po prostu zwracam wartości z dataSource (jak- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.tableViewDataSource sectionsCount]; }
).Oto mój cellForRow w klasie bazowej (nie trzeba zastępować w podklasach):
configureCell musi zostać zastąpione przez podklasy, a createCell zwraca UITableViewCell, więc jeśli chcesz niestandardową komórkę, również ją zastąp.
Po skonfigurowaniu podstawowych rzeczy (w rzeczywistości w pierwszym projekcie, który korzysta z takiego schematu, potem ta część może być ponownie wykorzystana) pozostaje
BaseTableViewController
podklasa:Przesłonięcie configCell (zwykle przekształca się w zapytanie źródła danych o obiekt o ścieżkę indeksu i przekazanie go do metody configWithXXX: komórki lub uzyskanie reprezentacji obiektu UITableViewCell jak w odpowiedzi użytkownika 4051)
Zastąp didSelectRowAtIndexPath: (oczywiście)
Napisz podklasę BaseDataSource, która zajmie się pracą z niezbędną częścią Modelu (załóżmy, że istnieją 2 klasy,
Account
aLanguage
więc podklasy będą to AccountDataSource i LanguageDataSource).I to wszystko dotyczy części widoku tabeli. W razie potrzeby mogę opublikować kod na GitHub.
Edycja: niektóre rekomendacje można znaleźć na stronie http://www.objc.io/issue-1/lighter-view-controllers.html (która ma link do tego pytania) oraz artykuł towarzyszący o kontrolerach tableview.
źródło
Uważam, że model musi podać tablicę obiektów o nazwie ViewModel lub viewData zawartych w konfiguratorze cellConfigurator. CellConfigurator przechowuje CellInfo potrzebną do usunięcia z pamięci i skonfigurowania komórki. przekazuje komórce trochę danych, aby mogła ona sama się skonfigurować. działa to również z sekcją, jeśli dodasz jakiś obiekt SectionConfigurator, który zawiera CellConfigurators. Zacząłem używać tego jakiś czas temu, po prostu dając komórce viewData i miałem ViewController zajmujący się usuwaniem kolejki z komórki. ale przeczytałem artykuł wskazujący na to repozytorium gitHub.
https://github.com/fastred/ConfigurableTableViewController
może to zmienić sposób, w jaki do tego podchodzisz.
źródło
Niedawno napisałem artykuł o wdrażaniu delegatów i źródeł danych dla UITableView: http://gosuwachu.gitlab.io/2014/01/12/uitableview-controller/
Główną ideą jest podzielenie obowiązków na osobne klasy, takie jak fabryka ogniw, fabryka sekcji i zapewnienie ogólnego interfejsu dla modelu, który wyświetli UITableView. Poniższy schemat wyjaśnia wszystko:
źródło
W następstwie SOLID zasady będą rozwiązywać wszelkiego rodzaju problemy jak te.
Jeśli chcesz się swoimi klasami mieć tylko jeden odpowiedzialność, należy zdefiniować odrębny
DataSource
iDelegate
zajęcia i po prostu wstrzyknąć je dotableView
właściciela (może byćUITableViewController
lubUIViewController
czy cokolwiek innego). W ten sposób przezwyciężysz rozłąkę .Ale jeśli chcesz mieć czysty i czytelny kod i chcesz pozbyć się tego ogromnego pliku viewController i jesteś w Swif , możesz do tego użyć
extension
s. Rozszerzenia pojedynczej klasy mogą być zapisywane w różnych plikach i wszystkie mają do siebie dostęp. Ale to jest naprawdę rozwiązuje problem SoC, jak wspomniałem.źródło