VIPER is complex, eng, en wel – anders. Er zit een vleugje gif onder de oppervlakte van VIPER en dat kan pijn doen. Maar na verloop van tijd neemt de pijn af, en wordt het een van de remedies om je code te structureren. Bye bye gif. Hallo geluk.
Dit is waar iOS-ontwikkelaars zich mee kunnen identificeren:
Het VIPER ontwerppatroon is een bron van verwarring & misverstand voor veel ontwikkelaars. De voors & tegens en het praktische gebruik van dit patroon zijn onduidelijk. Na het werken aan applicaties met de MVC, MVVM, MVVM + RxSwift patronen, wilde ik een diepgaand begrip krijgen van dit venijnig klinkende ontwerppatroon en zien waar de buzz (hiss?) allemaal over ging. Dientengevolge, besloot ik de volgende hypothese te testen:
Als ontwikkelaar zou ik het VIPER-ontwerppatroon willen gebruiken om herbruikbare modules te bouwen.
We hebben dit bericht geschreven in de hoop dat het u in staat stelt om te evalueren of VIPER al dan niet de juiste optie is voor uw project of framework.
VIPER
Een paar mensen bij Mutual Mobile wilden hun testdekking voor hun iOS-apps verbeteren. Als gevolg daarvan, kwamen ze met het patroon zelf. Meer informatie over waar het vandaan komt, is hier te vinden.
Letterlijk gesproken is Viper een backronym dat staat voor View, Interactor, Presenter, Entity, Router.
Theoretisch gesproken, bij gebruik van Viper je applicatie of kader zou worden opgesplitst in VIPER “modules” – herbruikbare & module componenten. In diagramvorm vertaalt zich dat als volgt:
View: De interface-laag legt de weergave vast, werkt afbeeldingen en labels bij, geeft gebruikersinvoer door aan de presentator
Interactor: Bedrijfslogica, manipulatie van gegevens, transformatie van modellen, interageert met API Services & klassen voor gegevensbeheer. Geeft uitvoer door aan de presentator
Presentator: Formatteert van de interactor ontvangen gegevens voor de view, ontvangt input van view en geeft deze door aan de interactor
Entity: Een eenvoudig gegeven dat vertegenwoordigt wat nodig is om de VIPER-module te laten functioneren
Router: Verantwoordelijk voor module navigatie en applicatie navigatie
VIPER in code werkt niet in dezelfde volgorde als de letters die u leest. Met andere woorden… de acties in uw code zullen meer in de onderstaande volgorde verlopen:
Interactor → Router , Entity, & Extern → Presenter → View
VIPER wordt meer als IREPV… Dat is een woord voor iemand, ergens.
EXPERIMENT
We valideerden de hypothese van het gebruik van VIPER in een herbruikbare module door het bouwen van een framework. Het raamwerk is gericht op het afspelen van video-inhoud. Het afspelen van video gaat gepaard met UI updates, data downloading & data synchronisatie. Deze interacties bewezen dat een video afspeel module een waardevolle kandidaat zou zijn voor een VIPER gestructureerde module.
OVERVIEW
VIPER modules zijn protocol-georiënteerd. Elke functie, eigenschap, invoer en uitvoer wordt geïmplementeerd via een protocol.
Als gevolg daarvan gebruikten we het idee van een:
Contract: een swift-bestand waarin alle protocollen worden beschreven die in de VIPER-module worden gebruikt. Het is vergelijkbaar met een objective-C header-bestand. Codefragment hieronder:
//*** 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()}
We wilden ook dat ontwikkelaars van dit framework een gemakkelijke manier zouden hebben om VIPER-modules te configureren.
Daarom hebben we het idee van een:
Builder gebruikt: Een eenvoudig object dat wordt gebruikt om de VIPER-module te configureren en afhankelijkheden in te stellen. Hij installeert de presenter en de interactor. Uiteindelijk stuurt het de view terug naar de viewcontroller.
public class VPKVideoPlaybackBuilder: VPKVideoPlaybackBuilderProtocol {
//Feedpublic static func vpk_buildViewInCell(for videoType:VPKVideoType, at indexPath: NSIndexPath, bij 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))
}}
We hebben ook een protocol gebruikt om alle afhankelijkheden voor de module in te stellen:
class VPKDependencyManager: VPKDependencyManagerProtocol {
statische func videoView(met interactor:VPKVideoPlaybackInteractorProtocol &VPKVideoPlaybackInteractorInputProtocol, en 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
}}
Het FRAMEWORK: VIDEOPLAYBACKKIT
Laten we eens kijken naar de user story:
**PLAY A VIDEO **
Met behulp van AVFOUNDATION weten we het volgende:
- Moet AVPlayer gebruiken
- AVPlayer geeft je een AVPlayerLayer om aan je view toe te voegen
- AVPlayer kost tijd om voor te bereiden – vooral met remote video urls
- Remote video data heeft tijd nodig om te downloaden
-
Twee primaire view acties
- Play
- Pause
-
Resultaten van deze acties:
- AVPlayer ontvangt video url om data voor te downloaden
- Wanneer klaar, retourneert de AVPlayer een AVPlayerLayer
- De AVPlayerLayer moet worden toegevoegd aan een view
- Op pauze, moet AVPlayer het afspelen stoppen
**IREPV (AKA VIPER) & AVKit, AVFoundation **
INPUTS EN OUTPUTS
Nou dit is de sleutel tot het begrijpen van het patroon, het kalmeren van je hersenen, het vermijden van de val in rabbithole complexiteiten.
Belangrijkste les 1: Begrijp de inputs en de outputs van je module.
View
- Input: didTapPlay
- Output: Presenter didTapPlay
Presenter
- Input: didTapPlay ontvangt van view
- Output: onPlayerLayerSuccess(playerLayer) – voert dit uit naar de presenter
Interactor
- Input: didTapPlay – vertelt de externe manager
-
Output: onVideoPlayerLayerSuccess – geeft spelerlaag door aan de presentator
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(_ seconden: TimeInterval)}
Als de view eenmaal de AVPlayerLayer van de presentator ontvangt, kan hij zijn interface opnieuw laden.
protocol VPKVideoViewProtocol: class {func reloadInterface(with playerLayer: AVPlayerLayer)}
INTERACTOR EN PRESENTER RELATIES
De relaties tussen de onderdelen van VIPER zijn anders dan die van andere iOS-patronen. Elk object houdt elkaar vast (zwak natuurlijk). Je kunt het zien alsof de een de gedelegeerde is voor de ander. Zie het voorbeeld hieronder:
-
De Presenter houdt een zwakke verwijzing naar de interactor. De presentator geeft ALLEEN om het invoerprotocol van de interactor. Het doorsturen van de “didTapPlay” invoer.
protocol VPKVideoPlaybackPresenterProtocol: klasse {var interactor: VPKVideoPlaybackInteractorInputProtocol? { get set }//weak}
-
De Presenter stuurt het invoerbericht door; de Presenter is echter ook afhankelijk van de interactors kennis van de video entiteitsgegevens.
public class VPKVideoPlaybackPresenter:VPKVideoPlaybackPresenterProtocol {func didTapVideoView() {guard let safeVideoUrl = interactor?.videoType.videoUrl anders {return }interactor?.didTapVideo(videoURL: safeVideoUrl, at:self.indexPath)}}
PROTOCOL CONFORMANCE
De relatie tussen presentator en interactor is het belangrijkste deel van de datastroom.
De Presentator geeft om de Interactors Output
De Interactor geeft om de Presentators Input
Zie hieronder:
De presentator conformeert zich aan het uitvoerprotocol van de Interactor.
//MARK: INTERACTOR --> Presenterextension VPKVideoPlaybackPresenter:VPKVideoPlaybackInteractorOutputProtocol { func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer) {videoView?.reloadInterface(with: playerLayer)
}}
De Interactor gebruikt de zwakke eigenschap van de presentator om de uitvoergegevens door te geven.
public class VPKVideoPlaybackInteractor:VPKVideoPlaybackInteractorProtocol {
zwak var presenter: VPKVideoPlaybackInteractorOutputProtocol?
func playbackManager(_: VPKVideoPlaybackManager, didPrepareplayerLayer: AVPlayerLayer) {presenter?.onVideoLoadSuccess(playerLayer)
}}
DE PRO’S
DE CONS
RECOMMENDATION
Wij adviseren een vorm van VIPER te gebruiken als:
- Jouw team openstaat voor het leren van een nieuw patroon
- Jouw team ten minste 1 week de tijd heeft om het te leren, te begrijpen en ermee te experimenteren
- Het gemakkelijker is om een testgestuurde aanpak te ondersteunen vanwege de ontkoppelde aard van het patroon
- Je er enthousiast over bent 🙂
- Teamleden zijn verantwoordelijk voor verschillende modules
We raden het gebruik van een vorm van VIPER niet aan als:
- Uw team is reactief, en features veranderen bijna dagelijks
- U bouwt een kleine applicatie die een experiment is en gebruikt wordt om een probleem te testen
- U geeft niet om tests
VIPER zal schitteren met lange termijn projecten, applicaties waar beveiliging en business logica hoog zijn, en wanneer testen moet worden geïmplementeerd.
U zult merken dat uw code schoner wordt. U zult in staat zijn om functies & modules onder teamleden gemakkelijk te scheiden. Als er een UI opmaak fout is, weet je meteen dat je de Presenter class moet controleren. Als er een logische bug is, weet je dat het uit de interactor komt. Meer duidelijkheid, minder venijn.
ADDITIONELE INFO
Dit onderwerp is gepresenteerd op try! Swift NYC 2017. De presentatie is hier te vinden.
Alle extra try! Swift talks zijn hier te vinden.