VIPER ist komplex, beängstigend und – nun ja – anders. Es gibt einen Hauch von Gift unter der Oberfläche von VIPER und das kann wehtun. Doch mit der Zeit lässt der Schmerz nach, und es wird zu einem der Heilmittel für die Strukturierung Ihres Codes. Bye bye Gift. Hallo Glück.
Hier ist, was iOS-Entwickler nachvollziehen können:
Das VIPER-Entwurfsmuster ist eine Quelle der Verwirrung & Missverständnisse für viele Entwickler. Die Vor- & und Nachteile und der praktische Nutzen dieses Musters sind unklar. Nachdem ich an Anwendungen mit den Mustern MVC, MVVM, MVVM + RxSwift gearbeitet habe, wollte ich dieses bösartig klingende Entwurfsmuster gründlich verstehen und sehen, was es mit dem Getöse (Zischen?) auf sich hat. Daher beschloss ich, die folgende Hypothese zu testen:
Als Entwickler würde ich gerne das VIPER-Entwurfsmuster verwenden, um wiederverwendbare Module zu erstellen.
Wir haben diesen Beitrag in der Hoffnung geschrieben, dass er Sie in die Lage versetzt, zu beurteilen, ob VIPER die richtige Option für Ihr Projekt oder Framework ist.
VIPER
Ein paar Leute bei Mutual Mobile wollten die Testabdeckung ihrer iOS-Apps verbessern. Daraufhin haben sie das Muster selbst entwickelt. Mehr dazu, woher es stammt, finden Sie hier.
Wörtlich genommen ist Viper ein Backronym, das für View, Interactor, Presenter, Entity, Router steht.
Theoretisch gesehen würde Ihre Anwendung oder Ihr Framework bei der Verwendung von Viper in VIPER-„Module“ – wiederverwendbare & Modulkomponenten – unterteilt werden. In Diagrammform sieht das folgendermaßen aus:
View: Die Oberflächenschicht legt die Ansicht an, aktualisiert Bilder und Beschriftungen, übergibt Benutzereingaben an den Präsentator
Interactor: Geschäftslogik, Manipulation von Daten, Transformation von Modellen, interagiert mit API-Diensten &Datenmanager-Klassen. Übergibt die Ausgabe an den Presenter
Presenter: Formatiert vom Interaktor empfangene Daten für die Ansicht, empfängt Eingaben von der Ansicht und gibt sie an den Interaktor weiter
Entity: Ein einfaches Datenelement, das repräsentiert, was für die Funktion des VIPER-Moduls benötigt wird
Router: Verantwortlich für die Modul- und Anwendungsnavigation
VIPER im Code funktioniert nicht in der gleichen Reihenfolge wie die Buchstaben, die Sie lesen. Mit anderen Worten… die Aktionen in Ihrem Code werden eher in der folgenden Reihenfolge ablaufen:
Interactor → Router , Entity, & Externals → Presenter → View
VIPER wird mehr wie IREPV… Das ist ein Wort für jemanden, irgendwo.
EXPERIMENT
Wir haben die Hypothese der Verwendung von VIPER in einem wiederverwendbaren Modul durch den Aufbau eines Frameworks validiert. Das Framework ist auf die Wiedergabe von Videoinhalten ausgerichtet. Das Abspielen von Videos beinhaltet Aktualisierungen der Benutzeroberfläche, das Herunterladen von Daten & und die Synchronisierung von Daten. Diese Interaktionen bewiesen, dass ein Videowiedergabemodul ein lohnender Kandidat für ein strukturiertes VIPER-Modul wäre.
ÜBERSICHT
VIPER-Module sind protokollorientiert. Jede Funktion, Eigenschaft, Eingabe und Ausgabe wird über ein Protokoll implementiert.
Als Ergebnis haben wir die Idee eines:
Contract: eine Swift-Datei, die alle im VIPER-Modul verwendeten Protokolle beschreibt. Sie ist vergleichbar mit einer Objective-C-Header-Datei. Code-Schnipsel unten:
//*** Interactor//*//*protocol VPKVideoPlaybackInteractorProtocol: class {
init(entity: VPKVideoType)}
protocol VPKVideoPlaybackInteractorInputProtocol: class {
var presenter: VPKVideoPlaybackInteractorOutputProtocol? { get set }var videoType: VPKVideoType { get set }var remoteImageURL: URL? { get set }var localImageName: String? { get set }
var playbackManager: (VPKVideoPlaybackManagerInputProtocol &VPKVideoPlaybackManagerOutputProtocol &VPKVideoPlaybackManagerProtocol)? { get set }
// PRESENTER -> INTERACTORfunc didTapVideo(videoURL: URL, at indexPath: NSIndexPath?)func didScrubTo(_ timeInSeconds: TimeInterval)func didSkipBack(_ seconds: Float)func didSkipForward(_ seconds: Float)func didToggleViewExpansion()func didMoveOffScreen()func didReuseInCell()}
Wir wollten den Entwicklern dieses Frameworks auch eine einfache Möglichkeit bieten, VIPER-Module zu konfigurieren.
Als Ergebnis haben wir die Idee eines:
Builders verwendet: Ein einfaches Objekt, das dazu dient, das VIPER-Modul zu konfigurieren und Abhängigkeiten einzurichten. Es instanziiert den Presenter und den Interactor. Letztendlich gibt er die Ansicht an den Viewcontroller zurück.
public class VPKVideoPlaybackBuilder: VPKVideoPlaybackBuilderProtocol {
//Feedpublic static func vpk_buildViewInCell(for videoType:VPKVideoType, at indexPath: NSIndexPath, bei playbackBarTheme:ToolBarTheme = .normal, completion viewCompletion:VideoViewClosure) {
let interactor = VPKVideoPlaybackInteractor(entity:videoType)let presenter: VPKVideoPlaybackPresenterProtocol &VPKVideoPlaybackInteractorOutputProtocol =VPKVideoPlaybackPresenter(with: false, showInCell: nil,playbackTheme: playbackBarTheme)viewCompletion(VPKDependencyManager.videoView(with:interactor, and: presenter))
}}
Wir haben auch ein Protokoll verwendet, um alle Abhängigkeiten für das Modul einzurichten:
class VPKDependencyManager: VPKDependencyManagerProtocol {
static func videoView(with interactor:VPKVideoPlaybackInteractorProtocol &VPKVideoPlaybackInteractorInputProtocol, and presenter:VPKVideoPlaybackPresenterProtocol &VPKVideoPlaybackInteractorOutputProtocol) -> VPKVideoView {
let videoView = VPKVideoView(frame: .zero)let playbackBarView: VPKPlaybackControlViewProtocol =VPKPlaybackControlView(theme: presenter.playbackTheme ?? .normal)let videoPlaybackManager: VPKVideoPlaybackManagerInputProtocol &VPKVideoPlaybackManagerOutputProtocol &VPKVideoPlaybackManagerProtocol = VPKVideoPlaybackManager.shared//Dependency setuppresenter.interactor = interactorinteractor.presenter = presentervideoView.presenter = presentervideoView.playbackBarView = playbackBarViewpresenter.playbackBarView = playbackBarViewplaybackBarView.presenter = presenterpresenter.videoView = videoViewinteractor.playbackManager = videoPlaybackManagerreturn videoView
}}
THE FRAMEWORK: VIDEOPLAYBACKKIT
Betrachten wir die User Story:
**Wiedergabe eines Videos **
Durch AVFOUNDATION wissen wir folgendes:
- Must use AVPlayer
- AVPlayer will give you an AVPlayerLayer to add on your view
- AVPlayer takes time to prepare – besonders bei entfernten Video-URLs
- Ferne Videodaten benötigen Zeit zum Herunterladen
-
Zwei primäre Ansichtsaktionen
- Wiedergabe
- Pause
-
Ergebnisse dieser Aktionen:
- AVPlayer empfängt Video-Url zum Herunterladen von Daten für
- Wenn bereit, gibt der AVPlayer einen AVPlayerLayer zurück
- Der AVPlayerLayer muss zu einer Ansicht hinzugefügt werden
- Bei Pause muss der AVPlayer die Wiedergabe stoppen
**IREPV (AKA VIPER) & AVKit, AVFoundation **
EIN- UND AUSGÄNGE
Dies ist der Schlüssel zum Verständnis des Musters, zur Beruhigung des Gehirns und zur Vermeidung des Sturzes in die Komplexität des Kaninchenbaus.
Hauptlektion 1: Verstehe die Ein- und Ausgänge deines Moduls.
Ansicht
- Eingang: didTapPlay
- Ausgang: Presenter didTapPlay
Presenter
- Input: didTapPlay erhält von view
- Output: onPlayerLayerSuccess(playerLayer) – gibt dies an den Presenter
Interactor
- Input: didTapPlay – teilt dies dem externen Manager mit
-
Output: onVideoPlayerLayerSuccess – übergibt die Playerebene an den Präsentator
protocol VPKVideoPlaybackInteractorOutputProtocol: class {
var progressTime: TimeInterval { get set }
//INTERACTOR –> PRESENTERfunc onVideoResetPresentation()func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer)func onVideoLoadFail(_ error: String)func onVideoDidStartPlayingWith(_ duration: TimeInterval)func onVideoDidStopPlaying()func onVideoDidPlayToEnd()func onVideoPlayingFor(_ seconds: TimeInterval)}
Wenn die Ansicht den AVPlayerLayer vom Präsentator erhält, kann sie ihre Schnittstelle neu laden.
protocol VPKVideoViewProtocol: class {func reloadInterface(with playerLayer: AVPlayerLayer)}
INTERACTOR UND PRESENTER RELATIONSHIP
Die Beziehungen zwischen den Teilen von VIPER unterscheiden sich von denen anderer iOS-Muster. Jedes Objekt hält an jedem anderen fest (natürlich nur schwach). Man kann es sich so vorstellen, dass das eine der Delegierte für das andere ist. Siehe das folgende Beispiel:
-
Der Presenter hält einen schwachen Verweis auf den Interaktor. Der Presenter interessiert sich NUR für das Eingabeprotokoll des Interaktors. Die Weiterleitung der „didTapPlay“ Eingabe.
protocol VPKVideoPlaybackPresenterProtocol: class {var interactor: VPKVideoPlaybackInteractorInputProtocol? { get set }//weak}
-
Der Presenter leitet die Eingangsnachricht weiter; allerdings ist der Presenter auch von der Kenntnis des Interaktors über die Videoentitätsdaten abhängig.
public class VPKVideoPlaybackPresenter:VPKVideoPlaybackPresenterProtocol {func didTapVideoView() {guard let safeVideoUrl = interactor?.videoType.videoUrl else {return }interactor?.didTapVideo(videoURL: safeVideoUrl, at:self.indexPath)}}
PROTOCOL CONFORMANCE
Die Beziehung zwischen Presenter und Interaktor ist der wichtigste Teil des Datenflusses.
Der Präsentator kümmert sich um die Ausgabe des Interaktors
Der Interaktor kümmert sich um die Eingabe des Präsentators
Siehe unten:
Der Presenter hält sich an das Ausgabeprotokoll des Interactors.
//MARK: INTERACTOR --> Presenterextension VPKVideoPlaybackPresenter:VPKVideoPlaybackInteractorOutputProtocol { func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer) {videoView?.reloadInterface(with: playerLayer)
}}
Der Interactor verwendet die weak-Eigenschaft des Presenters, um die Ausgabedaten zurückzugeben.
public class VPKVideoPlaybackInteractor:VPKVideoPlaybackInteractorProtocol {
weak var presenter: VPKVideoPlaybackInteractorOutputProtocol?
func playbackManager(_: VPKVideoPlaybackManager, didPrepareplayerLayer: AVPlayerLayer) {presenter?.onVideoLoadSuccess(playerLayer)
}}
Die PROS
Die Nachteile
EMPFEHLUNG
Wir empfehlen die Verwendung einer Form von VIPER, wenn:
- Ihr Team offen ist, ein neues Muster zu lernen
- Ihr Team mindestens 1 Woche Zeit hat, um es zu lernen, zu verstehen und damit zu experimentieren
- Ein testgetriebener Ansatz aufgrund der entkoppelten Natur des Musters leichter zu unterstützen
- Sie sind begeistert davon 🙂
- Teammitglieder sind für verschiedene Module verantwortlich
Wir empfehlen die Verwendung einer Form von VIPER nicht, wenn:
- Ihr Team ist reaktiv und Funktionen ändern sich fast täglich
- Sie bauen eine kleine Anwendung, die ein Experiment ist und zum Testen eines Problems verwendet wird
- Sie kümmern sich nicht um Tests
VIPER wird bei langfristigen Projekten, Anwendungen, bei denen Sicherheit und Geschäftslogik hoch sind, und wenn Tests implementiert werden müssen, glänzen.
Sie werden feststellen, dass Ihr Code sauberer wird. Sie werden in der Lage sein, Funktionen & Module unter den Teammitgliedern leicht zu trennen. Wenn es einen Fehler in der UI-Formatierung gibt, wissen Sie sofort, dass Sie die Presenter-Klasse überprüfen müssen. Wenn es einen logischen Fehler gibt, wissen Sie, dass er aus dem Interaktor stammt. Mehr Klarheit, weniger Gift.
ZUSÄTZLICHE INFO
Dieses Thema wurde auf der try! Swift NYC 2017. Die Präsentation ist hier zu finden.
Alle weiteren try! Swift-Vorträge finden Sie hier.