Saubere schnelle Architektur für Router und Datenübergabe

Hallo Leser!

In einem früheren Artikel habe ich über den VIP- Zyklus der Clean Swift- Architektur gesprochen. Jetzt werden wir eines der wichtigsten Themen ansprechen - den Übergang und die Übertragung von Daten zwischen Szenen.



Theorie


Die Router- Komponente ist für die Navigations- und Datenübertragungslogik verantwortlich, die Teil der Szene ist (natürlich optional). Es wird im ViewController zusammen mit dem Interactor und dem Presenter initialisiert.

Der Router implementiert zwei Protokolle - RoutingLogic und DataPassing , die wir mit unserer Funktionalität füllen werden. RoutingLogic sollte die Methoden enthalten, die für den Übergang zu einer bestimmten Szene verantwortlich sind. DataPassing enthält die Variable dataStore , die auf den Protocol DataStore verweist . Interactor Scenes implementiert das DataStore- Protokoll und arbeitet mit den darin gespeicherten Variablen. Der Router selbst enthält einen Link zum ViewController seiner Szene.

Über den Link zum ViewController springt der Router zwischen den Szenen. Dazu können Sie Segue verwenden oder programmgesteuert eine Szene erstellen, zu der Sie einen Übergang vornehmen möchten. Welche Methode verwendet wird, ist nicht wichtig. Für uns ist es wichtig, einen Link zu einer Instanz der ViewController- Klasse zu haben, zu der wir wechseln.

Über den Link zum DataStore übertragen wir Daten vom Interactor einer Szene zum Interactor der Szene, zu der wir wechseln. Und wie bereits erwähnt, muss der Router wissen, wie das geht.

Übe


Zum Beispiel übertragen wir den Text von TextField auf Label einer anderen Szene. Betrachten wir zwei Arten des Übergangs zwischen Szenen - nach Segue und programmgesteuert.

Die Router- Klasse enthält 3 semantische Gruppen von Methoden:

  1. Methoden aus der RoutingLogic- Implementierung (routeTo)
  2. Navigationsmethoden (navigieren zu, Übergang ohne Segue)
  3. Methoden zur Datenübertragung (passDataTo, wenn Daten zur Übertragung vorhanden sind)



Wenn wir beispielsweise beim Klicken auf eine Schaltfläche einen Übergang durch Segue vornehmen , müssen wir in ViewController die Methode prepare (for: sender :) überschreiben. Mit dieser Erweiterung können Sie automatisch Methoden vom Router mit dem Namen Segue aufrufen.

Das Überschreiben der Vorbereitung (für: Absender :) ist optional, wenn Sie mit Segue arbeiten. Sie können es aus dem Code ausschließen und performSegue (withIdentifier: sender :) in der Router- Methode aufrufen . Die Vorbereitung ist nur erforderlich, wenn Sie Segue zusammen mit der Datenübertragung verwenden müssen.

final class HomeViewController: UIViewController {
// ...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Segue
if let scene = segue.identifier {
// , Router
// router?.routeToNAME(segue:)
let selector = NSSelectorFromString("routeTo\(scene)WithSegue:")
// ,
// Segue
if let router = router, router.responds(to: selector) {
router.perform(selector, with: segue)
}
}
}
// ...
}


Jetzt kommen wir endlich zu dem interessantesten - dem Router- Code. Das Beispiel enthält Kommentare, daher werden nur wichtige Punkte berücksichtigt.

In diesem Router arbeiten wir mit zwei Szenen - Home und Detail . Der Übergang von der Home- Szene erfolgt ebenfalls auf zwei Arten - von Segue und programmgesteuert . Daten werden von der Home- Szene in die Detail- Szene übertragen.

Alle Methoden im RoutingLogic- Protokoll müssen nach dem routeToNAME- Prinzip benannt werden, wobei NAME der Name des Segues (Bezeichners) ist, den wir bei der Arbeit mit dem Storyboard angeben. Dies ist nicht nur für die Benutzerfreundlichkeit und Schönheit der Verwendung erforderlich, sondern auch für unseren Methodenselektor zur Vorbereitung (für: Absender :) ViewController , den wir zuvor neu definiert haben.

Auch in der HomeRouter- Klasse gibt es Methoden, die mit navigTo und passDataTo beginnen . Die ersteren sind für die Übergangslogik verantwortlich, während die letzteren für die Datenübertragung verantwortlich sind. Die navigateTo-Methoden werden nur erstellt, wenn der Übergang programmgesteuert erfolgt.

Im Beispiel haben wir eine routeToDetail (segue :) -Methode. Der Parameter segue ist seitdem optional Die Methode enthält eine Implementierung, mit der Sie sie ohne Verwendung von Segue aufrufen können. In beiden Übergangsfällen erhalten wir nicht optionale Werte für unsere HomeViewController'a- und HomeDataStore'a- Home- Szene sowie Links zu ViewController- und DataStore-Detailszene . Hierbei ist zu beachten, dass detailDS eine Variable ist und mit dem Parameter pass -through (inout) an die Methode passDataToDetail übergeben wird. Das ist wichtig, weil Ohne Inout müssen wir alle DataStore- Protokolle als "nur mit Klassen implementierbar" (Protokoll DetailDataStore: class) markieren. Dies bringt viele Schwierigkeiten mit sich, einschließlich der Erfassung starker Links.

import UIKit
/// @objc
/// prepare View Controller'e
@objc protocol HomeRoutingLogic {
/// Detail View Controller
func routeToDetail(segue: UIStoryboardSegue?)
}
protocol HomeDataPassing {
var dataStore: HomeDataStore? { get }
}
final class HomeRouter: NSObject, HomeRoutingLogic, HomeDataPassing {
// MARK: - Private
// MARK: - Public
weak var viewController: HomeViewController?
var dataStore: HomeDataStore?
// MARK: - HomeRoutingLogic
func routeToDetail(segue: UIStoryboardSegue?) {
if let segue = segue {
//
// Segue
// Detail View Controller
// Data Store Router'e
guard
let homeDS = dataStore,
let detailVC = segue.destination as? DetailViewController,
var detailDS = detailVC.router?.dataStore
else { fatalError("Fail route to detail") }
// , , ,
// ""
passDataToDetail(source: homeDS, destination: &detailDS)
} else {
// ,
// Segue
// Detail View Controller Storyboard'a
// Data Store Router'e
guard
let viewController = viewController,
let homeDS = dataStore,
let storyboard = viewController.storyboard,
let detailVC = storyboard.instantiateViewController(withIdentifier: "Detail") as? DetailViewController,
var detailDS = detailVC.router?.dataStore
else { fatalError("Fail route to detail") }
passDataToDetail(source: homeDS, destination: &detailDS)
// , ""
navigateToDetail(source: viewController, destination: detailVC)
}
}
// MARK: - Navigation
private func navigateToDetail(source: HomeViewController, destination: DetailViewController) {
source.navigationController?.pushViewController(destination, animated: true)
}
// MARK: - Passing data
/// destination inout,
/// Data Store
private func passDataToDetail(source: HomeDataStore, destination: inout DetailDataStore) {
// HomeDataStore DetailDataStore
destination.message = source.message
}
}


Fazit


Das ist alles. Vielen Dank für das Lesen bis zum Ende! Unten werde ich einen Link zum Projekt hinterlassen, wenn Sie den Artikel in Aktion ausprobieren möchten.

Artikelserie


  1. Übersicht über die saubere Swift-Architektur
  2. Router- und Datenübergabe in Clean Swift-Architektur (Sie sind hier)
  3. Arbeiter sauberer schneller Architektur
  4. Unit-Tests in der Clean Swift-Architektur
  5. Ein Beispiel für eine einfache Online-Shop-Architektur Clean Swift

Link zum Projekt
Hilfe beim Schreiben eines Artikels: Bastien

Source: https://habr.com/ru/post/de454032/


All Articles