VIPER è complesso, spaventoso, e beh – diverso. C’è un pizzico di veleno sotto la superficie di VIPER e può far male. Tuttavia, con il tempo il dolore si attenua, e diventa una delle cure per strutturare il tuo codice. Ciao ciao veleno. Ciao felicità.
Ecco a cosa possono riferirsi gli sviluppatori iOS:
Il design pattern VIPER è una fonte di confusione & incomprensione per molti sviluppatori. I pro &contro e l’uso pratico di questo pattern non sono chiari. Dopo aver lavorato su applicazioni con i pattern MVC, MVVM, MVVM + RxSwift, ho voluto ottenere una comprensione approfondita di questo design pattern dal suono vizioso e vedere di cosa si trattava il brusio (sibilo?). Di conseguenza, ho deciso di testare la seguente ipotesi:
Come sviluppatore vorrei usare il design pattern VIPER per costruire moduli riutilizzabili.
Abbiamo scritto questo post sperando che vi permetta di valutare se VIPER è l’opzione giusta per il vostro progetto o framework.
VIPER
Alcuni ragazzi di Mutual Mobile volevano migliorare la copertura dei test sulle loro applicazioni iOS. Di conseguenza, hanno inventato il modello stesso. Per saperne di più su dove è venuto si può trovare qui.
In senso letterale, Viper è un acronimo che sta per View, Interactor, Presenter, Entity, Router.
In senso teorico, quando si usa Viper l’applicazione o il framework sarebbe suddiviso in “moduli” VIPER – componenti riutilizzabili &. In forma di diagramma si traduce in questo:
Vista: Il livello dell’interfaccia disegna la vista, aggiorna le immagini e le etichette, passa l’input dell’utente al presentatore
Interattore: Logica di business, manipolazione dei dati, trasformazione dei modelli, interagisce con i servizi API & classi del gestore dei dati. Passa l’output al presentatore
Presentatore: Formatta i dati ricevuti dall’interactor per la vista, riceve input dalla vista e li passa all’interactor
Entity: Un semplice dato che rappresenta ciò che serve al modulo VIPER per funzionare
Router: Responsabile della navigazione del modulo e dell’applicazione
VIPER nel codice non funziona nella stessa sequenza delle lettere che leggete. In altre parole… le azioni nel vostro codice scorreranno più che altro nell’ordine seguente:
Interattore → Router , Entità, & Esterni → Presentatore → Vista
VIPER diventa più simile a IREPV… Questa è una parola per qualcuno, da qualche parte.
EXPERIMENT
Abbiamo validato l’ipotesi di usare VIPER in un modulo riutilizzabile costruendo un framework. Il framework è incentrato sulla riproduzione di contenuti video. La riproduzione di video comporta aggiornamenti dell’interfaccia utente, download di dati &sincronizzazione dei dati. Queste interazioni hanno dimostrato che un modulo di riproduzione video sarebbe un valido candidato per un modulo strutturato VIPER.
INVIO
I moduli VIPER sono orientati al protocollo. Ogni funzione, proprietà, ingresso e uscita è implementata tramite un protocollo.
Come risultato abbiamo usato l’idea di un:
Contratto: un file swift che descrive tutti i protocolli utilizzati nel modulo VIPER. È simile a un file di intestazione Objective-C. Frammento di codice qui sotto:
//*** 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()}
Vogliamo anche che gli sviluppatori di questo framework abbiano un modo semplice per configurare i moduli VIPER.
Come risultato abbiamo usato l’idea di un:
Builder: Un semplice oggetto utilizzato per configurare il modulo VIPER e impostare le dipendenze. Istanzia il presenter e l’interactor. Infine restituisce la vista al viewcontroller.
public class VPKVideoPlaybackBuilder: VPKVideoPlaybackBuilderProtocol {
//Feedpublic static func vpk_buildViewInCell(for videoType:VPKVideoType, at indexPath: NSIndexPath, con 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))
}}
Abbiamo anche usato un protocollo per impostare tutte le dipendenze del modulo:
class VPKDependencyManager: VPKDependencyManagerProtocol {
static func videoView(with interactor:VPKVideoPlaybackInteractorProtocol &VPKVideoPlaybackInteractorInputProtocol, e 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
}}
IL QUADRO: VIDEOPLAYBACKKIT
Consideriamo la storia utente:
**PLAY A VIDEO **
Utilizzando AVFOUNDATION sappiamo quanto segue:
- Deve usare AVPlayer
- AVPlayer ti darà un AVPlayerLayer da aggiungere alla tua vista
- AVPlayer richiede tempo per prepararsi – in particolare con gli URL video remoti
- I dati video remoti hanno bisogno di tempo per essere scaricati
-
Due azioni primarie della vista
- Play
- Pause
-
Risultati di queste azioni:
- AVPlayer riceve l’url del video per cui scaricare i dati
- Quando è pronto, AVPlayer restituisce un AVPlayerLayer
- L’AVPlayerLayer deve essere aggiunto a una vista
- In pausa, AVPlayer deve fermare la riproduzione
**IREPV (AKA VIPER) & AVKit, AVFoundation **
INPUTS E OUTPUTS
Ora questa è la chiave per capire il modello, calmare il tuo cervello, evitare la caduta in complessità da coniglio.
Lezione principale 1: capire gli ingressi e le uscite del vostro modulo.
Vista
- Input: didTapPlay
- Output: Presenter didTapPlay
Presenter
- Input: didTapPlay riceve dalla vista
- Output: onPlayerLayerSuccess(playerLayer) – lo invia al presenter
Interactor
- Input: didTapPlay – dice al gestore esterno
-
Output: onVideoPlayerLayerSuccess – passa il livello del lettore al presentatore
protocollo 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)}
Una volta che la vista riceve l’AVPlayerLayer dal presenter, può ricaricare la sua interfaccia.
protocol VPKVideoViewProtocol: class {func reloadInterface(with playerLayer: AVPlayerLayer)}
Relazioni tra gli oggetti e il presentatore
Le relazioni tra i pezzi di VIPER sono diverse da quelle di altri modelli iOS. Ogni oggetto si aggrappa all’altro (debolmente, ovviamente). Si può pensare che uno sia il delegato dell’altro. Vedi l’esempio qui sotto:
-
Il presentatore tiene un riferimento debole all’interagente. Il presentatore si preoccupa SOLO del protocollo di input dell’interactor. L’inoltro dell’input “didTapPlay”.
protocollo VPKVideoPlaybackPresenterProtocol: class {var interactor: VPKVideoPlaybackInteractorInputProtocol? { get set }//weak}
-
Il Presentatore inoltra il messaggio di input; tuttavia, il Presentatore dipende anche dalla conoscenza degli interactor dei dati delle entità video.
public class VPKVideoPlaybackPresenter:VPKVideoPlaybackPresenterProtocol {func didTapVideoView() {guard let safeVideoUrl = interactor?.videoType.videoUrl else {return }interactor?.didTapVideo(videoURL: safeVideoUrl, at:self.indexPath)}}
PROTOCOLLO CONFORME
La relazione tra presentatore e interactor è la parte più importante del flusso dei dati.
Il presentatore si preoccupa dell’output degli interattori
L’interattore si preoccupa dell’input dei presentatori
Vedi sotto:
Il presentatore si conforma al protocollo di uscita dell’Interactor.
//MARK: INTERACTOR --> Presenterextension VPKVideoPlaybackPresenter:VPKVideoPlaybackInteractorOutputProtocol { func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer) {videoView?.reloadInterface(with: playerLayer)
}}
L’Interactor usa la proprietà debole del presentatore per passare indietro i dati di uscita.
public class VPKVideoPlaybackInteractor:VPKVideoPlaybackInteractorProtocol {
weak var presenter: VPKVideoPlaybackInteractorOutputProtocol?
func playbackManager(_: VPKVideoPlaybackManager, didPrepareplayerLayer: AVPlayerLayer) {presenter?.onVideoLoadSuccess(playerLayer)
}}
I PRO
I CONTRO
Raccomandazione
Si consiglia di utilizzare una forma di VIPER se:
- Il tuo team è aperto all’apprendimento di un nuovo pattern
- Il tuo team ha almeno 1 settimana per impararlo, capirlo e sperimentarlo
- È più facile sostenere un approccio test driven grazie alla natura disaccoppiata del pattern
- Sei entusiasta:)
- I membri del team sono responsabili di moduli diversi
Non consigliamo di usare una forma di VIPER se:
- Il tuo team è reattivo, e le caratteristiche cambiano quasi quotidianamente
- Stai costruendo una piccola applicazione che è un esperimento e viene usata per testare un problema
- Non ti importa dei test
VIPER brillerà con progetti a lungo termine, applicazioni dove la sicurezza e la logica di business sono alte, e quando i test devono essere implementati.
Vi accorgerete che il vostro codice diventerà più pulito. Sarete in grado di separare facilmente i moduli di funzionalità & tra i membri del team. Quando c’è un bug di formattazione dell’UI saprete immediatamente di controllare la classe Presenter. Quando c’è un bug logico, sapete che proviene dall’interactor. Più chiarezza, meno veleno.
Info aggiuntive
Questo argomento è stato presentato al try! Swift NYC 2017. La presentazione può essere trovata qui.
Tutti i discorsi aggiuntivi di try! Swift possono essere trovati qui.