Setzen Sie Ihre View Controller mit der Clean Swift (VIP)-Architektur auf Diät, Teil 1

Wenn Sie gerade erst mit der iOS-Entwicklung beginnen (oder sogar über eine anständige Menge an Erfahrung verfügen), werden Sie möglicherweise Situationen sehen, in denen Ihre View-Controller ziemlich groß werden. Vielleicht finden Sie das gesamte View-Setup, präsentieren andere View-Controller, erhalten den aktuellen Standort des Benutzers und API-Aufrufe werden alle im View-Controller definiert und ausgeführt.

Nicht nur, dass die View-Controller widerspenstig aktualisiert werden, wenn Sie aufgefordert werden, Unit- oder UI-Tests zu schreiben, kratzen Sie sich möglicherweise am Kopf, wenn Sie versuchen, herauszufinden, wie in aller Welt Sie Ihre API- und Geolocation-Aufrufe austauschen können, um Mocks zu verwenden
statt echt. Dies kann für jemanden, der gerade erst in den Bereich des Testens seines Codes einsteigt, besonders entmutigend sein, und Sie können an diesem Beispiel sehen, dass etwas getan werden muss, um Code sauberer und einfacher zu testen.

Wie Sie wahrscheinlich wissen, werden iOS-Apps mit dem erstellt Model View Controller (MVC)-Architektur. Wenn Ihre Ansichten Storyboards oder programmatischer UI-Code sind, der dem Benutzer präsentiert wird, und Modelle für die Geschäftslogik dienen, scheint dies der einzige verbleibende Platz zu sein
“alles andere” wäre der Controller (oder View-Controller im Fall von iOS). In der iOS-Community gibt es einen Witz, für den MVC eigentlich steht Massive View-ControllerSie können also sehen, dass dies etwas ist, was normalerweise passiert, wenn Sie Ihre Entwicklerfähigkeiten auf dieser mobilen Plattform verbessern.

Es ist nicht deine Schuld

Bevor ich mich mit einer Möglichkeit befasse, diese Situation zu entschärfen, wollte ich mir eine Sekunde Zeit nehmen, um darüber zu sprechen wie und warum das passiert. Nun, es gibt unzählige Gründe, die zu diesem Antimuster führen, aber ich wollte 3 hervorheben, um sie kurz anzusprechen: Definition, Ausbildungund Erfahrung.

Definition

EIN Controller anzeigen, oder einfacher ein Controller, kann im Grunde als „Klebstoff“ zwischen Ansichtslogik und Geschäftsdomänenlogik beschrieben werden. Formalere Definitionen können viel spezifischer und detaillierter sein, aber wenn Sie sich nicht wirklich in das Thema vertiefen, ist die Erklärung dessen, was ein View-Controller tut, eigentlich ziemlich vage. Wenn wir auf hoher Ebene darüber nachdenken, vielleicht Sie
könnte sagen, es entspricht dem Grundsatz der Einzelverantwortung. Wenn wir jedoch etwas tiefer graben, sehen wir, dass die moderne Verwendung des View-Controllers in iOS tatsächlich VIEL komplexer ist. Nur ein paar Beispiele:

  • Es ist nicht nur für die Darstellung der Benutzeroberfläche verantwortlich, sondern muss auch Benutzereingaben akzeptieren und verarbeiten.
  • Das Einrichten von Ansichten kann das Erstellen von Unteransichten und deren Interaktion, das Anpassen der Einschränkungen an den Bildschirm, das Übergeben von tatsächlichen Inhalten an diese Ansichten aus der Geschäftslogik und das Aktualisieren dieser Ansichten auf der Grundlage von Ereignissen umfassen.
  • Alle Protokolle, denen der Ansichtscontroller für Dienste oder Funktionen entsprechen muss.
  • Präsentieren oder Verwerfen anderer Ansichtscontroller.
  • Direkter Zugriff auf das übergeordnete Element UINavigationController, UITabBarControlleroder einen anderen Container-View-Controller.

Diese Beispiele sind auch keine benutzerdefinierten, seltenen Anwendungsfälle für eine Anwendung. Ganz im Gegenteil, Sie könnten jedes einzelne dieser Beispiele selbst in den trivialsten Projekten finden.

Ausbildung

Das Erlernen der iOS-Entwicklung (einschließlich Swift und/oder Objective-C) kann eine entmutigende Aufgabe sein. Aus diesem Grund nehmen Anfänger-Tutorials und sogar Apples eigene Dokumentation und Codebeispiele häufig “Abkürzungen” aus Gründen der Kürze, der einfachen Erklärung oder um ein Tutorial/Video auf einer angemessenen Länge zu halten. Oft werden diese schriftlichen oder aufgezeichneten Referenzen mit einem Haftungsausschluss versehen, dass der von ihnen gewählte Ansatz möglicherweise nicht die besten Praktiken verwendet, aber dies kann leider dazu führen, dass Entwickler Dinge auf weniger als ideale Weise zusammenstellen, insbesondere wenn sie oder er neu sind auch zum Programmieren.

Was noch schlimmer ist, diese Probleme können zu immer größeren „Schmerzpunkten“ werden, wenn die Anwendung wächst, und normalerweise würde an diesem Punkt die Bereinigung der technischen Schuld eine große und zeitaufwändige Umgestaltung erfordern. Ich bin nicht dagegen, Unterrichtsmaterial und Dokumentation so einfach wie möglich zu halten, um den Leser nicht zu verwirren und “direkt zum Punkt zu kommen”, um bei der Lösung zu helfen
ein Problem, kann jedoch zu einer unglücklichen und unbeabsichtigten Nebenwirkung führen.

Erfahrung

Um auf den obigen Abschnitt einzugehen, googeln selbst erfahrene Ingenieure täglich Dinge. Was Sie jedoch mit diesen Informationen tun, entspricht eher der Absicht, die ich zu vermitteln versuche, wenn ich spreche Erfahrung eher als die Anzahl der Jahre, die jemand programmiert hat. Bei dieser Unterscheidung geht es nicht einmal darum, dass ein Suchergebnis “falsch” und ein anderes “richtig” ist, sondern mehr noch:

  • Zu wissen, welche Lösung in einer Situation im Vergleich zu einer anderen am besten funktioniert.
  • Das Konzept der Lösung verstehen und in der Lage sein, Best Practices oder Refactoring anzuwenden, um den Anforderungen gerecht zu werden.
  • Geschärfte “Google-Fu”-Fähigkeiten zur Strukturierung der Suchanfrage, um die Suche auf den spezifischen Anwendungsfall einzugrenzen.

Selbst erfahrene Entwickler, die eine neue Sprache oder ein neues Framework lernen, können anfangs Schwierigkeiten haben, wenn sie oder er den „Jargon“ oder die Terminologie nicht kennt. Mit diesem Verständnis ist es leicht zu erkennen, dass Sie selbst mit der „zu unterscheidenden Erfahrung“ keine Grundlage haben, um zu beurteilen, was für eine Situation am besten ist, wenn Sie mit dem Themenmaterial nicht vertraut sind.

Zurück zu unserem regulären Programmablauf….

Nachdem wir uns nun ausführlich mit dem Problem befasst haben, stellt sich nun die Frage: Wie lösen wir das? Wie der Titel dieses Beitrags andeutet, werden wir uns mit einem befassen Designmuster das in letzter Zeit im Bereich der iOS-Entwicklung an Popularität gewonnen hat. Ich wäre jedoch nachlässig, die nicht zuerst zu erwähnen Dutzende auch von anderen Optionen. In Bezug auf die Softwarearchitektur sind die getroffenen Entscheidungen weitgehend subjektiv und situativ, und selbst fertige Entwurfsmuster oder Frameworks können oft mit anderen kombiniert werden, um ein besseres Ergebnis zu erzielen.

Davon abgesehen sind die beiden Optionen, nach denen Sie wahrscheinlich zuerst greifen sollten, die klassischen Designmuster, die ursprünglich im Buch vorgestellt wurden Entwurfsmuster: Elemente wiederverwendbarer objektorientierter Softwareund die FEST Prinzipien. Wenn Sie diese als Leitfäden verwenden, können Sie Ihre objektorientierte Programmiererfahrung erheblich verbessern, die sich über mehrere Sprachen und Frameworks erstreckt. Darüber hinaus haben sich viele andere Programmierparadigmen als beliebte Alternativen zur klassischen MVC-Struktur entwickelt, die in iOS-Apps vorhanden ist. Dazu gehören (sind aber definitiv nicht beschränkt auf):

Swift reinigen

Das Swift reinigen Architektur oder View-Interactor-Presenter (VIP) ist ein solches Designmuster, das aus der Asche der kaputten MVC-Implementierung des iOS-Ökosystems auferstanden ist. Es gibt dem View-Controller die Möglichkeit, genau das zu tun: die Sicht kontrollieren. Wie oben erwähnt, umfasst dies zwei Hauptaufgaben:

  1. Reagieren auf Ereignisse (Drittanbieter, Benutzerinteraktion, Lifecycle-Hooks anzeigen)
  2. Steuern, was auf dem Bildschirm angezeigt wird (anfängliche Einrichtung der Ansicht und spätere Aktualisierungen dieser Ansicht)

Das Problem der Massiver View-Controller schleicht sich ein, weil es meist einfach einfacher ist, die diversen Abhängigkeiten des Controllers direkt in die Klasse selbst einzubinden. Nehmen wir an, ich möchte eine API-Anforderung starten, wenn die viewDidAppear(...) Methode aufgerufen wird. Ich weiß, dass ich eine separate Klasse oder Schnittstelle für die API bereitstellen muss, aber dann füge ich die API-Schnittstelle einfach direkt in den View-Controller ein.

  init(myApiClient: ApiClient = ApiClient()) { ... }

  viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    myApiClient.getList() { ... }
  }

Dieses Beispiel ist einfach, aber es gibt zwei wichtige Punkte:

  1. Wir legen dem View-Controller Implementierungsdetails offen, die er nicht kennen muss. In diesem Fall kann ich nicht viel mehr für das Refactoring tun, aber wenn die API-Anfrage komplizierter eingerichtet wäre, würde sie immer mehr Implementierungsdetails offenlegen, die der View-Controller nicht wissen muss.

  2. Das Einbeziehen des API-Clients in den View-Controller ist nur eine weitere Abhängigkeit in der potenziell großen Liste von Abhängigkeiten, die in dieselbe Klasse gezogen werden. Auch wenn die Abhängigkeiten bestehen
    durch optionale Eigenschaften eingeschlossen werden, wird das Testen immer schwieriger, da jede Abhängigkeit entweder so eingeschlossen werden muss, dass sie nicht mit anderen Abhängigkeiten kollidiert, oder verspottet oder gekürzt werden müsste.

Es wäre viel sauberer, wenn jedes Ereignis, das der View-Controller verarbeiten musste, an eine übergeben werden könnte Ausgang zu handhaben, und jedes Mal, wenn die Ansicht aktualisiert werden musste, konnte von einem einzelnen gehandhabt werden Eingang. Im Wesentlichen macht das die View-Interactor-Presenter-Kombination. Ereignisse werden vom Ansichtscontroller an den Interaktor weitergegeben, der Präsentator nimmt das Ergebnis dieser Arbeit und teilt dem Ansichtscontroller mit, was in seiner Ansicht aktualisiert werden soll und wie.

xMDeTsR.png

Die des View-Controllers Ausgang ist der Interaktor, der Interaktor Ausgang ist der Moderator und schließlich der des Moderators Ausgang ist wieder der View-Controller. In ähnlicher Weise die des Ineractors Eingang ist der Ansichtscontroller, der des Moderators Eingang ist der Interaktor und der View-Controller Eingang ist der Moderator.

Diese Schleife eines angeschlossenen Controller-Interactor-Presentators wird oft als a bezeichnet Szeneund jede Klasse in der Szene ist mit ihrer verbunden Eingang und Ausgang durch ein Protokoll. Denken Sie im folgenden Beispiel daran, dass ich das Protokoll benannt habe OrdersViewControllerOutput und die Variable, die den Interaktor im View-Controller speichert output. Sie können diese jedoch nach Belieben benennen, um Verwirrung zu vermeiden. Du könntest sie zum Beispiel benennen OrdersBusinessLogic und interactor wenn das klarer wäre.

BestellungenInteractor.swift

protocol OrderListViewControllerOutput {
  func getOrderList()
}

class OrderListInteractor: OrderListViewControllerOutput {
  ...
  getOrderList() {
    ...
  }

OrdersViewController.swift

class OrderListViewController: ViewController {
  var output: OrdersViewControllerOutput

  init(output: OrdersViewControllerOutput = OrdersInteractor()) {
    ...
    self.output = output
  }

  viewDidAppear(_ animated: Bool) {
    ...
    output.getOrderList()
  }

Es gibt zwei wichtige Punkte, auf die ich bei diesem Refactor hinweisen möchte:

  1. Es besteht keine Notwendigkeit mehr, einzelne Abhängigkeiten vom View-Controller zu übergeben und zu verwalten, da der Interaktor die vom View-Controller kommenden Ereignisse und Aktionen verarbeitet. Tatsächlich müssen Sie die Abhängigkeiten auch nicht einmal an den Interaktor übergeben. Stattdessen kann man auch jede „Arbeitseinheit“ zusammen mit ihren Abhängigkeiten zu Worker-Klassen extrahieren, die der Interaktor einzeln aufrufen kann, aber diese werden in Teil 2 dieses Beitrags besprochen.

  2. Einen Interaktor zu haben, der einem Protokoll entspricht, macht es viel einfacher, ihn zu Testzwecken zu verspotten. Die verspottete Interaktorklasse kann an den Ansichtscontroller übergeben werden, und die Überprüfung, ob jede Methode des Interaktors vom Ansichtscontroller aufgerufen wird, wird trivial.

OrderListInteractorMock.swift

class OrderListInteractorMock: OrderListViewControllerOutput {
  var getOrdersListCalled = false
  var getSomeOtherFunctionCalled = false

  func getOrdersList() {
    getOrdersListCalled = true
  }

  func someOtherFunction() {
    getSomeOtherFunctionCalled = true
  }
}

Datenübergabe im VIP-Zyklus

Wir haben in den kommenden Artikeln noch viel zu behandeln, aber das Letzte, worüber ich in Teil 1 sprechen möchte, ist, wie Daten zwischen den verschiedenen Methoden im VIP-Zyklus ausgetauscht werden. Anstatt jedes Argument an eine Methode zu übergeben, können wir bauen Modelle für unser Szene. Diese Modelle sind nur grundlegend Strukturen zum Zwecke des “Verpackens” der Argumente in eine einzige Datenstruktur. Der View-Controller übergibt a Anfrage an den Interaktor, der Interaktor übergibt a Antwort an den Präsentator, und schließlich übergibt der Präsentator a Modell ansehen zurück zum View-Controller.

L5ioLcf.png

Im Code sehen die Modelle etwa so aus:

class OrderList {
  struct Request {
    var start: Int
    var end: Int
    var count: Int
  }

  struct Response {
    ...
  }

  struct ViewModel {
    ...
  }
}

Jetzt die OrderListViewControllerOutput kann seine Methodensignatur aktualisieren, um a zu akzeptieren OrderList.Request Struktur.

  protocol OrdersViewControllerOutput {
    func getOrderList(_ request: OrderList.Request)
  }

Die Datenübergabe auf diese Weise bietet zwei entscheidende Vorteile:

  1. Wenn ein zusätzliches Argument benötigt wird, müssen Sie nur die aktualisieren OrderList Modelle anstelle der Methodensignatur und Protokolldefinitionen.

  2. Das Mocken einer einfachen Datenstruktur im Vergleich zu Objekten mit mehreren Argumenten ist beim Schreiben von Tests viel einfacher.

Fortgesetzt werden…

Wie ich oben erwähnt habe, werden wir uns in kommenden Artikeln auch mit Presentern, Workern, Routern, Unit-Tests und vielem mehr befassen. Ich hoffe, das Lernen über Clean Swift war ein faszinierendes und aufregendes Thema, und ich freue mich darauf, es in meinen nächsten Beiträgen zu erweitern. Schauen Sie sich Teil 2 an und wie immer viel Spaß beim Programmieren!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *