Die iOS-Plattform wird jedes Jahr vielen Änderungen unterzogen. Darüber hinaus arbeiten Bibliotheken von Drittanbietern regelmäßig an der Arbeit mit dem Netzwerk, dem Zwischenspeichern von Daten, dem Rendern der Benutzeroberfläche über JavaScript und vielem mehr. Im Gegensatz zu all diesen Trends sprach
Pavel Gurov über eine Architekturlösung, die unabhängig von den Technologien, die Sie jetzt oder in einigen Jahren verwenden, relevant sein wird.
Mit ApplicationCoordinator können Sie die Navigation zwischen Bildschirmen erstellen und gleichzeitig eine Reihe von Problemen lösen. Unter der Katzendemo und Anweisungen zur schnellsten Umsetzung dieses Ansatzes.
Über den Sprecher: Pavel Gurov entwickelt iOS-Anwendungen in Avito.
Navigation

Das Navigieren zwischen Bildschirmen ist eine Aufgabe, mit der 100% von Ihnen konfrontiert sind, egal was Sie tun - ein soziales Netzwerk, ein Taxi oder eine Online-Bank. So beginnt die Anwendung bereits im Prototypenstadium, wenn Sie nicht einmal genau wissen, wie die Bildschirme aussehen, wie Animationen aussehen und ob die Daten zwischengespeichert werden. Die Bildschirme können leere oder statische Bilder sein, aber
die Navigationsaufgabe wird in der Anwendung angezeigt, sobald mehr als einer dieser Bildschirme vorhanden ist . Das heißt, fast sofort.

Die gängigsten Methoden zum Erstellen der Architektur von iOS-Anwendungen: MVc, MVVm und MVp beschreiben das Erstellen eines einzelnen Bildschirmmoduls. Es heißt auch, dass die Module voneinander wissen, miteinander kommunizieren usw. können. Es wird jedoch nur sehr wenig darauf geachtet, wie Übergänge zwischen diesen Modulen vorgenommen werden, wer über diese Übergänge entscheidet und wie Daten übertragen werden.
UlStoryboard + Segues
iOS out of the box bietet verschiedene Möglichkeiten, um das folgende Bildschirmszenario anzuzeigen:
- Das bekannte UlStoryboard + wechselt , wenn wir alle Übergänge zwischen Bildschirmen in einer Metadatei festlegen und sie dann aufrufen. Alles ist sehr praktisch und großartig.
- Container - wie der UINavigationController. UITabBarController, UIPageController oder möglicherweise selbstgeschriebene Container, die sowohl programmgesteuert als auch zusammen mit StoryBoards verwendet werden können.
- Methode vorhanden (_: animiert: Vervollständigung :). Dies ist nur eine Methode der UIController-Klasse.
Es gibt keine Probleme mit diesen Tools. Das Problem ist genau, wie sie häufig verwendet werden. UINavigationController, performSegue, prepareForSegue und presentViewController sind alle Eigenschaftsmethoden der UIViewController-Klasse. Apple schlägt vor, diese Tools im UIViewController selbst zu verwenden.

Der Beweis dafür ist der folgende.

Dies sind Kommentare, die in Ihrem Projekt angezeigt werden, wenn Sie eine neue Unterklasse von UIViewController mithilfe einer Standardvorlage erstellen. Es wird direkt geschrieben. Wenn Sie Segues verwenden und Daten entsprechend dem Szenario auf den nächsten Bildschirm übertragen müssen, sollten Sie: diesen ViewController von Segues abrufen; wissen, welcher Typ es sein wird; Wirf es auf diesen Typ und gib deine Daten dort weiter.
Dieser Ansatz für Probleme bei der Gebäudenavigation.
1. Starre Konnektivität von BildschirmenDies bedeutet, dass Bildschirm 1 über die Existenz von Bildschirm 2 Bescheid weiß. Er weiß nicht nur über seine Existenz Bescheid, er erstellt ihn möglicherweise auch oder nimmt ihn aus dem Übergang, weiß, um welchen Typ es sich handelt, und überträgt einige Daten an ihn.
Wenn wir unter bestimmten Umständen Bildschirm 3 anstelle von Bildschirm 2 anzeigen müssen, müssen wir den neuen Bildschirm 3 auf die gleiche Weise kennen, um ihn in Bildschirmcontroller 1 zu integrieren. Alles wird noch schwieriger, wenn die Controller 2 und 3 nicht nur von mehreren Stellen aus aufgerufen werden können von Bildschirm 1. Es stellt sich heraus, dass das Wissen über Bildschirm 2 und 3 an jeder dieser Stellen zusammengenäht werden muss.
Um dies zu tun, ist eine weitere Hälfte des Problems. Die Hauptprobleme beginnen, wenn Änderungen an diesen Übergängen vorgenommen oder all dies unterstützt werden müssen.
2. Ordnen Sie die Skript-Controller neu anDies ist auch wegen der Verbindung nicht so einfach. Um zwei ViewController auszutauschen, reicht es nicht aus, in das UlStoryboard zu wechseln und zwei Bilder auszutauschen. Sie müssen den Code für jeden dieser Bildschirme öffnen, ihn auf die Einstellungen des nächsten übertragen und seine Positionen ändern, was nicht sehr praktisch ist.
3. Datenübertragung gemäß SzenarioWenn Sie beispielsweise etwas auf Bildschirm 3 auswählen, müssen Sie die Ansicht auf Bildschirm 1 aktualisieren. Da wir anfangs nur einen ViewController haben, müssen wir die beiden ViewController - egal wie - irgendwie durch Delegierung oder irgendwie verbinden noch. Es wird noch schwieriger, wenn gemäß der Aktion auf Bildschirm 3 nicht ein Bildschirm, sondern mehrere gleichzeitig aktualisiert werden müssen, beispielsweise der erste und der zweite.

In diesem Fall kann auf die Delegierung nicht verzichtet werden, da die Delegierung eine Eins-zu-Eins-Beziehung ist. Jemand wird sagen, verwenden wir die Benachrichtigung, jemand - über einen gemeinsamen Status. All dies macht es schwierig, Datenflüsse in unserer Anwendung zu debuggen und zu verfolgen.
Wie sie sagen, ist es besser, einmal zu sehen als 100 Mal zu hören. Schauen wir uns ein spezielles Beispiel aus dieser Avito Services Pro-Anwendung an. Diese Anwendung richtet sich an Fachleute im Dienstleistungssektor, in denen es bequem ist, Ihre Bestellungen zu verfolgen, mit Kunden zu kommunizieren und nach neuen Bestellungen zu suchen.
Szenario - Auswahl einer Stadt beim Bearbeiten eines Benutzerprofils.

Hier ist ein Profilbearbeitungsbildschirm, wie er in vielen Anwendungen verwendet wird. Wir sind an einer Stadt interessiert.
Was ist hier los?
- Der Benutzer klickt auf die Zelle mit der Stadt und der erste Bildschirm entscheidet, dass es Zeit ist, den folgenden Bildschirm zum Navigationsstapel hinzuzufügen. Dies ist ein Bildschirm mit einer Liste der Bundesstädte (Moskau und St. Petersburg) und einer Liste der Regionen.
- Wenn der Benutzer auf dem zweiten Bildschirm eine Bundesstadt auswählt, versteht der zweite Bildschirm, dass das Skript abgeschlossen wurde, leitet die ausgewählte Stadt an die erste weiter und der Navigationsstapel rollt zurück zum ersten Bildschirm. Das Skript wird als vollständig betrachtet.
- Wenn der Benutzer einen Bereich auf dem zweiten Bildschirm auswählt, entscheidet der zweite Bildschirm, dass ein dritter Bildschirm vorbereitet werden muss, in dem eine Liste der Städte in diesem Bereich angezeigt wird. Wenn der Benutzer eine Stadt auswählt, wird diese Stadt zum ersten Bildschirm gesendet, rollt den Navigationsstapel und das Skript wird als vollständig betrachtet.
In diesem Diagramm werden die zuvor erwähnten Konnektivitätsprobleme als Pfeile zwischen dem ViewController angezeigt. Wir werden diese Probleme jetzt loswerden.
Wie machen wir das?- Wir verbieten uns im UIViewController, auf Container zuzugreifen, dh auf self.navigationController, self.tabBarController oder andere benutzerdefinierte Container, die Sie als Eigenschaftserweiterung erstellt haben. Jetzt können wir unseren Container nicht mehr aus dem Bildschirmcode nehmen und ihn bitten, etwas zu tun.

- Wir verbieten uns im UIViewController, die performSegue-Methode aufzurufen und Code in die prepareForSegue-Methode zu schreiben, wodurch der Bildschirm, der dem Skript folgt, konfiguriert wird. Das heißt, wir arbeiten nicht mehr mit Segue (mit Übergängen zwischen Bildschirmen) im UIViewController.

- Wir verbieten auch die Erwähnung anderer Controller in unserem spezifischen Controller : keine Initialisierungen, Datenübertragungen und das ist alles.

Koordinator
Da wir alle diese Verantwortlichkeiten aus dem UIViewController entfernen, benötigen wir eine neue Entität, die sie ausführt. Erstellen Sie eine neue Klasse von Objekten und nennen Sie sie den Koordinator.

Der Koordinator ist nur ein gewöhnliches Objekt, an das wir zu Beginn des NavigationControllers übergeben und die Start-Methode aufrufen. Denken Sie jetzt nicht darüber nach, wie es implementiert wird, sondern schauen Sie sich an, wie sich das Szenario für die Auswahl einer Stadt in diesem Fall ändern wird.
Jetzt beginnt es nicht damit, dass wir den Übergang zu einem bestimmten NavigationController-Bildschirm vorbereiten, sondern wir rufen die Start-Methode beim Koordinator auf und übergeben sie zuvor im NavigationController-Initialisierer. Der Koordinator versteht, dass es Zeit für den NavigationController ist, den ersten Bildschirm zu starten, was er auch tut.
Wenn der Benutzer eine Zelle mit einer Stadt auswählt, wird dieses Ereignis an den Koordinator weitergeleitet. Das heißt, der Bildschirm selbst weiß nichts - danach, wie man so sagt, zumindest eine Flut. Er sendet diese Nachricht an den Koordinator, und der Koordinator reagiert darauf mit (da er über einen Navigationscontroller verfügt), der den nächsten Schritt an ihn sendet - dies ist die Auswahl der Regionen.
Als nächstes klickt der Benutzer auf "Region" - genau das gleiche Bild - der Bildschirm selbst löst nichts, teilt dem Koordinator nur mit, dass der nächste Bildschirm geöffnet wird.
Wenn der Benutzer auf dem dritten Bildschirm eine bestimmte Stadt auswählt, wird diese Stadt über den Koordinator auch auf den ersten Bildschirm übertragen. Das heißt, eine Nachricht wird an den Koordinator gesendet, dass eine Stadt ausgewählt wurde. Der Koordinator sendet diese Nachricht an den ersten Bildschirm und rollt den Navigationsstapel auf den ersten Bildschirm.
Beachten Sie, dass die
Controller nicht mehr miteinander kommunizieren , entscheiden, wer als nächstes kommt, und keine Daten untereinander übertragen. Außerdem wissen sie nichts über ihre Umgebung.

Wenn wir die Anwendung im Rahmen einer dreischichtigen Architektur betrachten, sollte der ViewController idealerweise vollständig in die Präsentationsschicht passen und die Anwendungslogik so wenig wie möglich enthalten.
In diesem Fall verwenden wir den Koordinator, um die Logik der Übergänge in die darüber liegende Ebene herauszuholen und dieses Wissen aus dem ViewController zu entfernen.
Demo
Ein Präsentations- und Demo-
Projekt ist auf Github verfügbar. Nachfolgend finden Sie eine Demonstration während des Vortrags.
Dies ist das gleiche Szenario: Bearbeiten eines Profils und Auswählen einer Stadt darin.
Der erste Bildschirm ist der Benutzerbearbeitungsbildschirm. Es werden Informationen zum aktuellen Benutzer angezeigt: Name und ausgewählte Stadt. Es gibt eine Schaltfläche "Wählen Sie eine Stadt." Wenn wir darauf klicken, gelangen wir zum Bildschirm mit einer Liste der Städte. Wenn wir dort eine Stadt auswählen, erhält der erste Bildschirm diese Stadt.
Mal sehen, wie das im Code funktioniert. Beginnen wir mit dem Modell.
struct City { let name: String } struct User { let name: String var city: City? }
Die Modelle sind einfach:
- Eine Stadtstruktur mit einem Feldnamen, einer Zeichenfolge;
- Ein Benutzer, der auch einen Namen und eine Eigentumsstadt hat.
Als nächstes kommt
StoryBoard . Es beginnt mit einem NavigationController. Im Prinzip sind hier dieselben Bildschirme wie im Simulator: ein Benutzerbearbeitungsbildschirm mit einer Beschriftung und einer Schaltfläche sowie ein Bildschirm mit einer Liste von Städten, auf dem ein Tablet mit Städten angezeigt wird.
Benutzerbearbeitungsbildschirm
import UIKit final class UserEditViewController: UIViewController, UpdateableWithUser { // MARK: - Input - var user: User? { didSet { updateView() } } // MARK: - Output - var onSelectCity: (() -> Void)? @IBOutlet private weak var userLabel: UILabel? @IBAction private func selectCityTap(_ sender: UIButton) { onSelectCity?() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) updateView() } private func updateView() { userLabel?.text = "User: \(user?.name ?? ""), \n" + "City: \(user?.city?.name ?? "")" } }
Hier gibt es eine Eigenschaft Benutzer - dies ist der Benutzer, der außerhalb übertragen wird - der Benutzer, den wir bearbeiten werden. Wenn Sie hier den Benutzer festlegen, wird der didSet-Block aufgerufen, was zu einem Aufruf der lokalen updateView () -Methode führt. Alles, was diese Methode tut, ist einfach Informationen über den Benutzer auf das Etikett zu setzen, dh seinen Namen und den Namen der Stadt anzuzeigen, in der dieser Benutzer lebt.
Das gleiche passiert in der viewWillAppear () -Methode.
Der interessanteste Ort ist der Handler zum Klicken auf die Schaltfläche Stadtauswahl selectCityTap ().
Hier löst der Controller selbst nichts : Er erstellt keine Controller, ruft keinen Segue auf. Alles, was er tut, ist ein Rückruf - dies ist die zweite Eigenschaft unseres ViewControllers. Der onSelectCity-Rückruf hat keine Parameter. Wenn der Benutzer auf die Schaltfläche klickt, wird dieser Rückruf aufgerufen.
Stadtauswahlbildschirm
import UIKit final class CitiesViewController: UITableViewController { // MARK: - Output - var onCitySelected: ((City) -> Void)? // MARK: - Private variables - private let cities: [City] = [City(name: "Moscow"), City(name: "Ulyanovsk"), City(name: "New York"), City(name: "Tokyo")] // MARK: - Table - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return cities.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = cities[indexPath.row].name return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { onCitySelected?(cities[indexPath.row]) } }
Dieser Bildschirm ist ein UITableViewController. Die Liste der Städte hier ist festgelegt, kann aber von einem anderen Ort stammen. Weiter (// MARK: - Table -) ist ein ziemlich trivialer Tabellencode, der eine Liste von Städten in Zellen anzeigt.
Der interessanteste Ort hier ist der didSelectRowAt IndexPath-Handler, eine bekannte Methode für alle. Hier löst der Bildschirm selbst wieder nichts. Was passiert nach Auswahl der Stadt? Es ruft einfach einen Rückruf mit einem einzelnen Parameter "Stadt" auf.
Dies beendet den Code für die Bildschirme selbst. Wie wir sehen, wissen sie nichts über ihre Umgebung.
Koordinator
Kommen wir zum Link zwischen diesen Bildschirmen.
import UIKit protocol UpdateableWithUser: class { var user: User? { get set } } final class UserEditCoordinator { // MARK: - Properties private var user: User { didSet { updateInterfaces() } } private weak var navigationController: UINavigationController? // MARK: - Init init(user: User, navigationController: UINavigationController) { self.user = user self.navigationController = navigationController } func start() { showUserEditScreen() } // MARK: - Private implementation private func showUserEditScreen() { let controller = UIStoryboard.makeUserEditController() controller.user = user controller.onSelectCity = { [weak self] in self?.showCitiesScreen() } navigationController?.pushViewController(controller, animated: false) } private func showCitiesScreen() { let controller = UIStoryboard.makeCitiesController() controller.onCitySelected = { [weak self] city in self?.user.city = city _ = self?.navigationController?.popViewController(animated: true) } navigationController?.pushViewController(controller, animated: true) } private func updateInterfaces() { navigationController?.viewControllers.forEach { ($0 as? UpdateableWithUser)?.user = user } } }
Der Koordinator hat zwei Eigenschaften:
- Benutzer - der Benutzer, den wir bearbeiten werden;
- Der NavigationController, an den beim Start übergeben werden soll.
Es gibt ein einfaches init (), das diese Eigenschaft auffüllt.
Als nächstes folgt die Methode start (), mit der die Methode
ShowUserEditScreen () aufgerufen wird . Lassen Sie uns näher darauf eingehen. Diese Methode nimmt den Controller aus UIStoryboard und übergibt ihn an unseren lokalen Benutzer. Dann setzt er den SelectCity-Rückruf ein und schiebt diesen Controller in den Navigationsstapel.
Nachdem der Benutzer auf die Schaltfläche geklickt hat, wird der onSelectCity-Rückruf ausgelöst, wodurch die folgende private
ShowCitiesScreen () -Methode
aufgerufen wird .
Tatsächlich macht es fast dasselbe - es hebt einen etwas anderen Controller vom UIStoryboard ab, setzt den onCitySelected-Rückruf darauf und schiebt ihn in den Navigationsstapel - das ist alles, was passiert. Wenn der Benutzer eine bestimmte Stadt auswählt, wird dieser Rückruf ausgelöst, der Koordinator aktualisiert das Feld "Stadt" unseres lokalen Benutzers und rollt den Navigationsstapel zum ersten Bildschirm.
Da User eine Struktur ist, führt das Aktualisieren des Feldes "city" dazu, dass der didSet-Block aufgerufen wird bzw. die private Methode
updateInterfaces () aufgerufen wird. Diese Methode durchläuft den gesamten Navigationsstapel und versucht, jeden ViewController als UpdateableWithUser-Protokoll bereitzustellen. Dies ist das einfachste Protokoll, das nur eine Eigenschaft hat - Benutzer. Wenn dies erfolgreich ist, wirft er es an den aktualisierten Benutzer. Somit stellt sich heraus, dass unser ausgewählter Benutzer auf dem zweiten Bildschirm automatisch zum ersten Bildschirm springt.
Mit dem Koordinator ist alles klar, und das einzige, was hier noch zu zeigen ist, ist der Einstiegspunkt in unsere Bewerbung. Hier fängt alles an. In diesem Fall ist dies die didFinishLaunchingWithOptions-Methode unseres AppDelegate.
import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var coordinator: UserEditCoordinator! func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { guard let navigationController = window?.rootViewController as? UINavigationController else { return true } let user = User(name: "Pavel Gurov", city: City(name: "Moscow")) coordinator = UserEditCoordinator(user: user, navigationController: navigationController) coordinator.start() return true } }
Hier wird der Navigationscontroller aus dem UIStoryboard entnommen, ein Benutzer erstellt, den wir bearbeiten, mit einem Namen und einer bestimmten Stadt. Als nächstes erstellen wir unseren Koordinator mit User und NavigationController. Es ruft die start () -Methode auf. Der Koordinator wird auf das lokale Grundstück übertragen - das ist im Grunde alles. Das Schema ist recht einfach.
Ein- und Ausgänge
Es gibt einige Punkte, auf die ich näher eingehen möchte. Sie haben wahrscheinlich bemerkt, dass die Eigenschaft in userEditViewController mit einem Kommentar als Eingabe markiert ist und die Rückrufe dieser Controller als Ausgabe markiert sind.
Eine Eingabe sind alle Daten, die sich im Laufe der Zeit ändern können, sowie einige ViewController-Methoden, die von außen aufgerufen werden können. In UserEditViewController ist dies beispielsweise eine User-Eigenschaft - der Benutzer selbst oder sein City-Parameter können sich ändern.
Ein Exit ist ein Ereignis, das der Controller mit der Außenwelt kommunizieren möchte. In UserEditViewController ist dies ein Klick auf die Schaltfläche onSelectCity und auf dem Bildschirm zur Stadtauswahl ein Klick auf eine Zelle mit einer bestimmten Stadt. Ich wiederhole, die Hauptidee hier ist, dass der Controller nichts weiß und nichts gegen diese Ereignisse unternimmt. Er delegiert, um zu entscheiden, was zu tun ist, an jemand anderen.
In Objective-C habe ich es wegen ihrer schrecklichen Syntax nicht wirklich gemocht, Save Callbacks zu schreiben. In Swift ist dies jedoch viel einfacher. Die Verwendung von Rückrufen ist in diesem Fall eine Alternative zum bekannten Delegierungsmuster in iOS. Nur hier können wir, anstatt Methoden im Protokoll zu bestimmen und zu sagen, dass der Koordinator diesem Protokoll entspricht, und diese Methoden dann irgendwo separat schreiben, sofort sehr bequem eine Entität an einem Ort erstellen, einen Rückruf darauf setzen und alles tun.
Bei diesem Ansatz besteht im Gegensatz zur Delegierung zwar eine enge Verbindung zwischen dem Wesen des Koordinators und dem Bildschirm, da der Koordinator weiß, dass es ein bestimmtes Wesen des Bildschirms gibt.
Sie können dies auf die gleiche Weise wie bei der Delegierung mithilfe von Protokollen beseitigen.

Um Konnektivität zu vermeiden, können wir
den Ein- und Ausgang unseres Controllers mit einem Protokoll schließen .
Oben ist das CitiesOutput-Protokoll aufgeführt, für das genau eine Anforderung gilt: der onCitySelected-Rückruf. Links ist ein Analogon dieses Schemas auf Swift. Unser Controller hält dieses Protokoll ein und ermittelt den erforderlichen Rückruf. Wir tun dies, damit der Koordinator nichts über die Existenz der CitiesViewController-Klasse weiß. Aber irgendwann muss er den Ausgang dieses Controllers konfigurieren. Um alles anzukurbeln, fügen wir dem Koordinator eine Fabrik hinzu.

Die Factory verfügt über eine cityOutput () -Methode. Es stellt sich heraus, dass unser Koordinator keinen Controller erstellt und ihn nicht von irgendwoher erhält. Eine Fabrik wirft es auf ihn, das ein Objekt zurückgibt, das durch das Protokoll in der Methode geschlossen wurde, und er weiß nichts darüber, welche Klasse dieses Objekt ist.
Nun das Wichtigste - warum das alles?
Warum müssen wir ein weiteres zusätzliches Level einbauen, wenn es sowieso keine Probleme gab?Man kann sich diese Situation vorstellen: Ein Manager wird zu uns kommen und Sie bitten, A / B-Tests durchzuführen, um festzustellen, dass wir anstelle einer Liste von Städten die Wahl einer Stadt auf der Karte haben würden. Wenn in unserer Anwendung die Wahl der Stadt nicht an einem Ort, sondern in verschiedenen Koordinatoren, in verschiedenen Szenarien getroffen wurde, mussten wir an jedem Ort eine Flagge nähen, sie nach draußen werfen und auf dieser Flagge entweder den einen oder den anderen ViewController hissen. Dies ist nicht sehr praktisch.
Wir möchten dieses Wissen vom Koordinator entfernen. Daher könnte man dies an einem Ort tun. In der Fabrik selbst würden wir einen Parameter erstellen, mit dem die Fabrik entweder den einen oder den anderen vom Protokoll geschlossenen Controller zurückgibt. Beide hätten einen Rückruf bei CitySelected, und dem Koordinator wäre es im Prinzip egal, mit welchem dieser Bildschirme er arbeiten möchte - einer Karte oder einer Liste.
Zusammensetzung VS Vererbung
Der nächste Punkt, auf den ich eingehen wollte, ist die Komposition gegen Vererbung.

- Die erste Methode, wie unser Koordinator ausgeführt werden kann, besteht darin , die Komposition zu erstellen, wenn der NavigationController von außen an ihn übergeben und lokal als Eigenschaft gespeichert wird. Dies ist wie eine Komposition - wir haben einen NavigationController als Eigenschaft hinzugefügt.
- Auf der anderen Seite gibt es die Meinung, dass alles im UI-Kit enthalten ist und wir das Rad nicht neu erfinden müssen. Sie können den UI NavigationController einfach übernehmen und erben .
Jede Option hat ihre Vor- und Nachteile, aber persönlich scheint mir die
Zusammensetzung in diesem Fall besser geeignet zu sein als die Vererbung. Vererbung ist im Allgemeinen ein weniger flexibles Schema. Wenn wir beispielsweise die Navigation beispielsweise in UIPageController ändern müssen, können wir sie im ersten Fall einfach mit einem gemeinsamen Protokoll schließen, z. B. "Nächsten Bildschirm anzeigen", und den benötigten Container bequem ersetzen.
Aus meiner Sicht ist das wichtigste Argument, dass Sie alle unnötigen Methoden vor dem Endbenutzer in der Komposition verbergen. Es stellt sich heraus, dass er weniger wahrscheinlich stolpert. Sie belassen
nur die API, die benötigt wird , z. B. die Start-Methode - und das ist alles. Er hat keine Möglichkeit, den PushViewController, die PopViewController-Methode, aufzurufen, dh die Aktivitäten des Koordinators irgendwie zu stören. Alle Methoden der übergeordneten Klasse sind ausgeblendet.
Storyboards
Ich glaube, dass sie neben Segues besondere Aufmerksamkeit verdienen. Persönlich unterstütze
ich Segues , da Sie sich so schnell mit dem Skript vertraut machen können. Wenn ein neuer Entwickler eintrifft, muss er den Code nicht erklimmen. Storyboards helfen dabei. Selbst wenn Sie eine Schnittstelle mit dem Code erstellen, können Sie den leeren ViewController verlassen und die Schnittstelle mit dem Code erstellen, aber mindestens die Übergänge und den ganzen Punkt belassen. Die gesamte Essenz von Storyboards liegt in den Übergängen selbst und nicht im Layout der Benutzeroberfläche.
Glücklicherweise
schränkt der
Koordinatoransatz die Auswahl der Werkzeuge nicht ein . Wir können sicher Koordinatoren zusammen mit Segues verwenden. Aber wir müssen uns daran erinnern, dass wir jetzt nicht mehr mit Segues im UIViewController arbeiten können.

Daher müssen wir die onPrepareForSegue-Methode in unserer Klasse überschreiben. Anstatt etwas innerhalb des Controllers zu tun, werden wir diese Aufgaben über den Rückruf erneut an den Koordinator delegieren. Die onPrepareForSegue-Methode wird aufgerufen. Sie tun nichts selbst. Sie wissen nicht, um welche Art von Segue es sich handelt, um welchen Ziel-Controller es sich handelt. Es spielt für Sie keine Rolle. Sie werfen einfach alles in einen Rückruf, und der Koordinator wird es herausfinden. Er hat dieses Wissen, du brauchst dieses Wissen nicht.
Um alles einfacher zu machen, können Sie dies in einer bestimmten Basisklasse tun, um es nicht in jedem Controller einzeln zu überschreiben. In diesem Fall ist es für den Koordinator bequemer, mit Ihren Segues zu arbeiten.
Eine andere Sache, die ich mit dem Storyboard bequem finde, ist die Einhaltung der Regel, dass
ein Storyboard einem Koordinator entspricht . Dann können Sie alles erheblich vereinfachen, eine Klasse im Allgemeinen erstellen - den StoryboardCoordinator - und den RootType-Parameter darin generieren, den anfänglichen Navigationscontroller im Storyboard erstellen und das gesamte Skript darin einschließen.

Wie Sie sehen können, hat der Koordinator hier 2 Eigenschaften: navigationController; Der rootViewController unseres RootType ist generisch. Während der Initialisierung übergeben wir ihm keinen bestimmten Navigationscontroller, sondern ein Storyboard, von dem unsere Root-Navigation und sein erster Controller stammen. Auf diese Weise müssen wir nicht einmal Startmethoden aufrufen. Das heißt, Sie haben einen Koordinator erstellt, er hat sofort Navigation und sofort Root. Sie können die Navigation entweder modal anzeigen oder Root übernehmen und in die vorhandene Navigation verschieben und weiterarbeiten.
Unser UserEditCoordinator wird in diesem Fall einfach zu Typealias und ersetzt den Typ seines RootViewControllers durch den generischen Parameter.
Skriptdatenübertragung zurück
Lassen Sie uns über die Lösung des letzten Problems sprechen, das ich am Anfang skizziert habe. Dies ist die Übertragung von Daten zurück zum Skript.

Stellen Sie sich dasselbe Szenario für die Auswahl einer Stadt vor, aber jetzt können Sie nicht eine Stadt, sondern mehrere auswählen. Um dem Benutzer anzuzeigen, dass er mehrere Städte innerhalb derselben Region ausgewählt hat, wird auf dem Bildschirm mit einer Liste von Regionen eine kleine Zahl neben dem Namen der Region angezeigt, die die Anzahl der in dieser Region ausgewählten Städte anzeigt.
Es stellt sich heraus, dass die Aktion auf einem Controller (auf dem dritten) zu einer Änderung des Erscheinungsbilds mehrerer anderer gleichzeitig führen sollte. Das heißt, im ersten müssen wir in der Zelle mit der Stadt anzeigen, und im zweiten müssen wir alle Nummern in den ausgewählten Regionen aktualisieren.
Der Koordinator vereinfacht diese Aufgabe, indem er Daten zurück in das Skript überträgt. Dies ist jetzt so einfach wie das Weiterleiten von Daten gemäß dem Skript.
Was ist hier los? Der Benutzer wählt eine Stadt aus. Diese Nachricht wird an den Koordinator gesendet. Der Koordinator durchläuft, wie ich bereits in der Demo gezeigt habe, den gesamten Navigationsstapel und sendet aktualisierte Daten an alle interessierten Parteien. Dementsprechend kann ViewController seine Ansicht mit diesen Daten aktualisieren.
Refactoring von vorhandenem Code
Wie kann ich vorhandenen Code umgestalten, wenn Sie diesen Ansatz in eine vorhandene Anwendung mit MVc, MVVm oder MVp einbetten möchten?

Sie haben eine Reihe von ViewController. Das erste, was zu tun ist, ist, sie in Szenarien zu unterteilen, an denen sie teilnehmen. In unserem Beispiel gibt es drei Szenarien: Autorisierung, Profilbearbeitung, Band.

Wir wickeln nun jedes Szenario in unseren Koordinator ein. Wir sollten in der Lage sein, diese Skripte tatsächlich von einer beliebigen Stelle in unserer Anwendung aus zu starten. Dies sollte flexibel sein - der
Koordinator muss völlig autark sein .
Dieser Entwicklungsansatz bietet zusätzlichen Komfort. Es besteht darin, dass Sie, wenn Sie gerade mit einem bestimmten Szenario arbeiten, nicht jedes Mal darauf klicken müssen, wenn Sie es starten. Sie können es schnell beim Start starten, etwas darin bearbeiten und dann diesen temporären Start entfernen.
Nachdem wir uns für unsere Koordinatoren entschieden haben, müssen wir bestimmen, welches Szenario zum Start eines anderen führen kann, und aus diesen Szenarien einen Baum erstellen.

In unserem Fall ist der Baum einfach: LoginCoordinator kann den Profilbearbeitungskoordinator starten. Hier passt fast alles zusammen, aber ein sehr wichtiges Detail bleibt - unserem Schema fehlt ein Einstiegspunkt.

Dieser Einstiegspunkt ist ein spezieller Koordinator -
ApplicationCoordinator . Es wird von
AppDelegate erstellt und gestartet und steuert dann bereits die Logik auf Anwendungsebene,
dh welcher Koordinator startet gerade.
Wir haben uns nur eine sehr ähnliche Schaltung angesehen, nur ViewController anstelle von Koordinatoren, und wir haben sie so gestaltet, dass ViewController nichts voneinander wusste und keine Daten aneinander weitergab. Grundsätzlich kann das auch mit den Koordinatoren gemacht werden. Wir können in ihnen eine bestimmte Eingabe (Startmethode) und Ausgabe (onFinish-Rückruf) festlegen.
Koordinatoren werden unabhängig, wiederverwendbar und leicht testbar . Die Koordinatoren kennen sich nicht mehr und kommunizieren beispielsweise nur noch mit ApplicationCoordinator.
Sie müssen vorsichtig sein, denn wenn Ihre Anwendung über genügend dieser Skripte verfügt, kann sich ApplicationCoordinator in ein riesiges Gottobjekt verwandeln, das alle vorhandenen Skripte kennt - dies ist auch nicht sehr cool. Hier müssen wir schon schauen - vielleicht die Koordinatoren in Unterkoordinatoren unterteilen, dh über eine solche Architektur nachdenken, damit diese Objekte nicht zu unglaublichen Größen wachsen.
Obwohl die Größe nicht immer ein Grund für das Refactoring ist .
Wo soll ich anfangen?
Ich empfehle, von unten nach oben zu beginnen - implementieren Sie zuerst einzelne Skripte.

Um dieses Problem zu umgehen, können sie im UIViewController gestartet werden. Das heißt, solange Sie nicht über Root oder andere Koordinatoren verfügen, können Sie einen Koordinator erstellen und als temporäre Lösung vom UIViewController aus starten und ihn lokal in der Eigenschaft speichern (wie nextCoordinator oben). Wenn ein Ereignis eintritt, erstellen Sie, wie in der Demo gezeigt, eine lokale Eigenschaft, platzieren den Koordinator dort und rufen die Start-Methode auf. Alles ist sehr einfach.
Wenn dann alle diese Koordinatoren bereits fertig sind, sieht der Anfang von einem in dem anderen genau gleich aus. Haben Sie eine lokale Eigenschaft oder eine Art von Abhängigkeiten wie den Koordinator, fügen Sie all dies dort ein, damit es nicht wegläuft, und rufen Sie die Start-Methode auf.
Zusammenfassung
- Unabhängige Bildschirme und Skripte , die nichts voneinander wissen, kommunizieren nicht miteinander. Wir haben versucht, dies zu erreichen.
- Es ist einfach, die Reihenfolge der Bildschirme in der Anwendung zu ändern, ohne die Bildschirmcodes zu ändern. Wenn alles so gemacht wird, wie es sollte, sollte sich in der Anwendung nur der Bildschirmcode ändern, wenn sich das Skript ändert, sondern der Koordinatorcode.
- Vereinfachte Datenübertragung zwischen Bildschirmen und anderen Aufgaben, die eine Verbindung zwischen Bildschirmen implizieren.
- — , .
AppsConf 2018 8 9 — ! ( ) . — iOS Android, , , , .