VIPER este complex, înfricoșător și, ei bine, diferit. Există o urmă de venin sub suprafața lui VIPER și poate răni. Cu toate acestea, în timp, durerea se atenuează și devine unul dintre leacurile de structurare a codului dumneavoastră. La revedere, la revedere venin. Bună ziua fericire.
Iată la ce se pot raporta dezvoltatorii iOS:
Patronul de proiectare VIPER este o sursă de confuzie & neînțelegere pentru mulți dezvoltatori. Argumentele pro & contra și utilizarea practică a acestui tipar sunt neclare. După ce am lucrat la aplicații cu modelele MVC, MVVM, MVVM + RxSwift, am vrut să înțeleg în profunzime acest model de proiectare care sună vicios și să văd despre ce este vorba în această zvonistică (șuierătură?). Ca urmare, am decis să testez următoarea ipoteză:
În calitate de dezvoltator, aș dori să folosesc modelul de proiectare VIPER pentru a construi module reutilizabile.
Am scris această postare în speranța că vă împuternicește să evaluați dacă VIPER este sau nu opțiunea potrivită pentru proiectul sau cadrul dvs. de lucru.
VIPER
Câțiva oameni de la Mutual Mobile au vrut să își îmbunătățească acoperirea testelor pentru aplicațiile lor iOS. Ca urmare, au venit cu modelul în sine. Mai multe în ceea ce privește locul de unde a venit pot fi găsite aici.
Literalmente vorbind, Viper este un backronim care înseamnă View, Interactor, Presenter, Entity, Router.
Teoretic vorbind, în timp ce folosiți Viper, aplicația sau cadrul dvs. ar fi împărțit în „module” VIPER – componente de modul reutilizabile &. În formă de diagramă, se traduce astfel:
View: Stratul de interfață stabilește vizualizarea, actualizează imaginile și etichetele, transmite datele de intrare ale utilizatorului către prezentator
Interactor: Logica de afaceri, manipularea datelor, transformarea modelelor, interacționează cu serviciile API & clasele de gestionare a datelor. Transmite ieșirea către prezentator
Presentator: Formatează datele primite de la interactor pentru vizualizare, primește datele de intrare de la vizualizare și le transmite către interactor
Entity: O dată simplă care reprezintă ceea ce este necesar pentru ca modulul VIPER să funcționeze
Router: Responsabil de navigarea modulului și de navigarea aplicației
VIPER în cod nu funcționează în aceeași ordine ca literele pe care le citiți. Cu alte cuvinte… acțiunile din codul dvs. vor curge mai degrabă în ordinea de mai jos:
Interactor → Router , Entitate, & Externe → Prezentator → Vizualizare
VIPER devine mai mult ca IREPV… Aceasta este o vorbă pentru cineva, undeva.
EXPERIMENT
Am validat ipoteza de a folosi VIPER într-un modul reutilizabil prin construirea unui framework. Cadrul este axat pe redarea de conținut video. Redarea de conținut video implică actualizări ale interfeței utilizator, descărcarea de date & sincronizarea datelor. Aceste interacțiuni au dovedit că un modul de redare video ar fi un candidat valoros pentru un modul structurat VIPER.
VIEW
Modulele VIPER sunt orientate spre protocol. Fiecare funcție, proprietate, intrare și ieșire este implementată prin intermediul unui protocol.
Ca urmare, am folosit ideea de:
Contract: un fișier swift care descrie toate protocoalele utilizate în modulul VIPER. Este similar cu un fișier de antet objective-C. Fragment de cod de mai jos:
//*** 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 &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 didMoveMoveOffScreen()func didReuseInCell()}
Am dorit, de asemenea, ca dezvoltatorii acestui cadru să aibă o modalitate ușoară de a configura modulele VIPER.
Ca urmare, am folosit ideea unui:
Builder: Un obiect simplu folosit pentru a configura modulul VIPER și pentru a seta dependențele. Acesta instanțiază prezentatorul și interactorul. În cele din urmă, returnează vizualizarea către viewcontroller.
public class VPKVideoPlaybackBuilder: VPKVideoPlaybackBuilderProtocol {
//Feededpublic static func vpk_buildViewInCell(for videoType:VPKVideoType, at indexPath: NSIndexPath, cu 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))
}}}
Am folosit, de asemenea, un protocol pentru a configura toate dependențele pentru modul:
class VPKDependencyManager: VPKDependencyManagerProtocol {
static func videoView(with interactor:VPKVideoPlaybackInteractorProtocol &VPKVideoPlaybackInteractorInteractorInputProtocol, și 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
}}}
CADRU: VIDEOPLAYBACKKIT
Să luăm în considerare povestea utilizatorului:
**PLAY A VIDEO **
Utilizând AVFOUNDATION știm următoarele:
- Trebuie să folosiți AVPlayer
- AVPlayer vă va oferi un AVPlayerLayer pe care să-l adăugați la vizualizare
- AVPlayer are nevoie de timp pentru a se pregăti – în special cu urile video la distanță
- Datele video la distanță au nevoie de timp pentru a fi descărcate
-
Două acțiuni primare de vizualizare
- Play
- Pause
-
Rezultatele acestor acțiuni:
- AVPlayer primește url-ul video pentru care să descarce date
- Când este gata, AVPlayer returnează un AVPlayerLayer
- AVPlayerLayer trebuie adăugat la o vizualizare
- La pauză, AVPlayer trebuie să oprească redarea
**IREPV (AKA VIPER) & AVKit, AVFoundation **
INTRĂRI ȘI IEȘIRI
Acum, aceasta este cheia pentru a înțelege modelul, pentru a vă calma creierul, pentru a evita căderea în complexitatea vizuinii de iepure.
Lecția principală 1: Înțelegeți intrările și ieșirile modulului dumneavoastră.
Vezi
- Intrare: didTapPlay
- Scoatere: Prezentator didTapPlay
Prezentator
- Intrare: didTapPlay primește de la vizualizare
- Seșire: onPlayerLayerSuccess(playerLayer) – transmite acest lucru către prezentator
Interactor
- Intrare: didTapPlay primește de la vizualizare
- Intrare: didTapPlay – spune managerului extern
-
Output: onVideoPlayerLayerSuccess – transmite stratul playerului către prezentator
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)}
După ce vizualizarea primește AVPlayerLayer de la prezentator, aceasta își poate reîncărca interfața.
protocol VPKVideoViewProtocol: class {func reloadInterface(with playerLayer: AVPlayerLayer)}
RELAȚIA ÎNTRE INTERACTOR ȘI PREZENTATOR
Relațiile dintre piesele din VIPER sunt diferite de cele din alte modele iOS. Fiecare obiect se ține unul de celălalt (în mod slab, desigur). Vă puteți gândi că unul este delegatul celuilalt. Vedeți exemplul de mai jos:
-
Prezentatorul deține o referință slabă la interactor. Prezentatorului îi pasă NUMAI de protocolul de intrare al interactorului. Transmiterea intrării „didTapPlay”.
protocol VPKVideoPlaybackPresenterProtocol: class {var interactor: VPKVideoPlaybackInteractorInteractorInputProtocol? { get set }//weak}
-
Prezentatorul transmite mesajul de intrare; cu toate acestea, prezentatorul depinde, de asemenea, de cunoștințele interactorilor cu privire la datele entității video.
public class VPKVideoPlaybackPresenter:VPKVideoPlaybackPresenterProtocol {func didTapVideoView() {guard let safeVideoUrl = interactor?.videoType.videoUrl else {return }interactor?.didTapVideo(videoURL: safeVideoUrl, at:self.indexPath)}}
CONFORMITATEA PROTOCOLULUI
Relația dintre prezentator și interactor este cea mai importantă parte a fluxului de date.
Prezentatorul se interesează de ieșirea interactorilor
Interactorul se interesează de intrarea prezentatorilor
A se vedea mai jos:
Prezentatorul se conformează protocolului de ieșire al interactorului.
//MARK: INTERACTOR --> Presenterextension VPKVideoPlaybackPresenter:VPKVideoPlaybackInteractorOutputProtocol { func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer) {videoView?.reloadInterface(with: playerLayer)
}}
Interactorul folosește proprietatea slabă a prezentatorului pentru a transmite înapoi datele de ieșire.
public class VPKVideoPlaybackInteractor:VPKVideoPlaybackInteractorProtocol {
fără var prezentator: VPKVideoPlaybackInteractorOutputProtocol?
func playbackManager(_: VPKVideoPlaybackManager, didPrepareplayerLayer: AVPlayerLayer) {presenter?.onVideoLoadSuccess(playerLayer)
}}}
PROS
CONS
RECOMANDARE
Recomandăm utilizarea unei forme de VIPER dacă:
- Echipa dvs. este deschisă să învețe un nou model
- Echipa dvs. are cel puțin 1 săptămână pentru a învăța, înțelege și experimenta cu acesta
- Este mai ușor să susțineți o abordare bazată pe teste datorită naturii decuplate a modelului
- Sunteți entuziasmat de acesta 🙂
- Membrii echipei sunt responsabili pentru module diferite
Nu recomandăm utilizarea unei forme de VIPER dacă:
- Echipa dumneavoastră este reactivă, iar caracteristicile se schimbă aproape zilnic
- Construiți o aplicație mică, care este un experiment și este folosită pentru a testa o problemă
- Nu vă interesează testele
VIPER va străluci în cazul proiectelor pe termen lung, al aplicațiilor în care securitatea și logica de afaceri sunt ridicate și când trebuie implementate teste.
Vă veți observa că codul dvs. devine mai curat. Veți putea separa cu ușurință modulele de caracteristici & între membrii echipei. Atunci când există o eroare de formatare a interfeței utilizator veți ști instantaneu că trebuie să verificați clasa Presenter. Când există un bug logic, veți ști că provine de la interactor. Mai multă claritate, mai puțin venin.
INFORMAȚII SUPLIMENTARE
Acest subiect a fost prezentat la try! Swift NYC 2017. Prezentarea poate fi găsită aici.
Toate informațiile suplimentare din try! Swift talks pot fi găsite aici.