VIPER est complexe, effrayant, et bien – différent. Il y a un soupçon de venin sous la surface de VIPER et cela peut faire mal. Pourtant, avec le temps, la douleur s’atténue, et il devient l’un des remèdes à la structuration de votre code. Bye bye venin. Bonjour le bonheur.
Voici ce à quoi les développeurs iOS peuvent s’identifier :
Le patron de conception VIPER est une source de confusion &de malentendus pour de nombreux développeurs. Les avantages & inconvénients et l’utilisation pratique de ce pattern ne sont pas clairs. Après avoir travaillé sur des applications avec les patrons MVC, MVVM, MVVM + RxSwift, j’ai voulu acquérir une compréhension approfondie de ce patron de conception à consonance vicieuse et voir ce qu’était le buzz (sifflement ?). En conséquence, j’ai décidé de tester l’hypothèse suivante :
En tant que développeur, j’aimerais utiliser le patron de conception VIPER pour construire des modules réutilisables.
Nous avons écrit ce post en espérant qu’il vous donne les moyens d’évaluer si oui ou non VIPER est la bonne option pour votre projet ou votre framework.
VIPER
Quelques personnes chez Mutual Mobile voulaient améliorer leur couverture de test sur leurs applications iOS. En conséquence, ils sont venus avec le motif lui-même. Plus en ce qui concerne l’origine de ce motif peut être trouvé ici.
Littéralement parlant, Viper est un backronyme qui signifie View, Interactor, Presenter, Entity, Router.
Théoriquement parlant, tout en utilisant Viper votre application ou votre framework serait décomposé en « modules » VIPER – des composants de modules réutilisables &. Sous forme de diagramme, cela se traduit par ceci :
Vue : La couche d’interface met en page la vue, met à jour les images et les étiquettes, transmet les entrées de l’utilisateur au présentateur
Interacteur : Logique d’entreprise, manipulation des données, transformation des modèles, interagit avec les classes du gestionnaire de données des services API &. Passe la sortie au présentateur
Presenter : Formate les données reçues de l’interacteur pour la vue, reçoit les entrées de la vue et les transmet à l’interacteur
Entité : Une donnée simple représentant ce qui est nécessaire au fonctionnement du module VIPER
Routeur : Responsable de la navigation du module et de l’application
VIPER en code ne fonctionne pas dans le même ordre que les lettres que vous lisez. En d’autres termes… les actions dans votre code s’écouleront plutôt dans l’ordre ci-dessous :
Interacteur → Routeur , Entité, & Externes → Présentateur → Vue
VIPER devient plus comme IREPV… C’est un mot à quelqu’un, quelque part.
EXPERIMENT
Nous avons validé l’hypothèse d’utiliser VIPER dans un module réutilisable en construisant un framework. Le framework est axé sur la lecture de contenu vidéo. La lecture d’une vidéo implique des mises à jour de l’interface utilisateur, le téléchargement de données &la synchronisation des données. Ces interactions ont prouvé qu’un module de lecture vidéo serait un candidat valable pour un module structuré VIPER.
OVERVIEW
Les modules VIPER sont orientés protocole. Chaque fonction, propriété, entrée et sortie est implémentée via un protocole.
En conséquence, nous avons utilisé l’idée d’un :
Contrat : un fichier swift décrivant tous les protocoles utilisés dans le module VIPER. Il est similaire à un fichier d’en-tête objective-C. Extrait de code ci-dessous:
//*** 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 }
// PRESENTATEUR -> 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()}
Nous voulions également que les développeurs de ce framework aient un moyen facile de configurer les modules VIPER.
En conséquence, nous avons utilisé l’idée d’un:
Builder : Un objet simple utilisé pour configurer le module VIPER et mettre en place les dépendances. Il instancie le présentateur et l’interacteur. Finalement, il renvoie la vue au viewcontroller.
public class VPKVideoPlaybackBuilder: VPKVideoPlaybackBuilderProtocol {
//Feedpublic static func vpk_buildViewInCell(for videoType:VPKVideoType, at indexPath : NSIndexPath, avec 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))
}
Nous avons également utilisé un protocole pour mettre en place toutes les dépendances du module:
class VPKDependencyManager: VPKDependencyManagerProtocol {
static func videoView(with interactor :VPKVideoPlaybackInteractorProtocol &VPKVideoPlaybackInteractorInputProtocol, et 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
}
LE CADRE : VIDEOPLAYBACKKIT
Prenons en compte la user story :
**PLAY A VIDEO **
En utilisant AVFOUNDATION, nous savons ce qui suit :
- Doit utiliser AVPlayer
- AVPlayer vous donnera un AVPlayerLayer à ajouter sur votre vue
- AVPlayer prend du temps à préparer -. particulièrement avec les urls vidéo distantes
- Les données vidéo distantes ont besoin de temps pour être téléchargées
-
Deux actions de vue primaires
- Play
- Pause
-
Résultats de ces actions :
- L’AVPlayer reçoit l’url de la vidéo pour télécharger des données
- Quand il est prêt, l’AVPlayer renvoie un AVPlayerLayer
- L’AVPlayerLayer doit être ajouté à une vue
- En cas de pause, l’AVPlayer doit arrêter la lecture
**IREPV (AKA VIPER) & AVKit, AVFoundation **
INPUTS ET SORTIES
Maintenant, c’est la clé pour comprendre le modèle, calmer votre cerveau, éviter la chute dans les complexités du trou de lapin.
La grande leçon 1 : comprendre les entrées et les sorties de votre module.
Vue
- Entrée : didTapPlay
- Sortie : Présentateur didTapPlay
Présentateur
- Input : didTapPlay reçoit de la vue
- Output : onPlayerLayerSuccess(playerLayer) – sort ceci au présentateur
Interacteur
- Input : didTapPlay – indique au gestionnaire externe
-
Output : onVideoPlayerLayerSuccess – passe la couche du lecteur au présentateur
protocole 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)}
Une fois que la vue reçoit le AVPlayerLayer du présentateur, elle peut recharger son interface.
protocol VPKVideoViewProtocol: class {func reloadInterface(with playerLayer: AVPlayerLayer)}
RELATIONS ENTRE INTERACTEURS ET PRÉSENTATEURS
Les relations entre les pièces de VIPER sont différentes de celles des autres modèles iOS. Chaque objet s’accroche à l’autre (faiblement bien sûr). Vous pouvez penser que l’un est le délégué de l’autre. Voir l’exemple ci-dessous :
-
Le présentateur détient une référence faible à l’interacteur. Le présentateur se soucie UNIQUEMENT du protocole d’entrée de l’interacteur. Le transfert de l’entrée « didTapPlay ».
protocole VPKVideoPlaybackPresenterProtocol : class {var interactor : VPKVideoPlaybackInteractorInputProtocol ? { get set }//weak}
-
Le Presenter transmet le message d’entrée ; cependant, le Presenter dépend également des connaissances des interactors sur les données de l’entité vidéo.
public class VPKVideoPlaybackPresenter:VPKVideoPlaybackPresenterProtocol {func didTapVideoView() {guard let safeVideoUrl = interactor ?.videoType.videoUrl else {return }interactor ?.didTapVideo(videoURL : safeVideoUrl, at:self.indexPath)}}
PROTOCOL CONFORMANCE
La relation entre le présentateur et l’interacteur est la partie la plus importante du flux de données.
Le présentateur se soucie de la sortie des interacteurs
L’interacteur se soucie de l’entrée des présentateurs
Voir ci-dessous :
Le présentateur se conforme au protocole de sortie de l’Interacteur.
//MARK: INTERACTOR --> Presenterextension VPKVideoPlaybackPresenter:VPKVideoPlaybackInteractorOutputProtocol { func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer) {videoView?.reloadInterface(with: playerLayer)
}
L’Interacteur utilise la propriété faible du présentateur pour renvoyer les données de sortie.
public class VPKVideoPlaybackInteractor:VPKVideoPlaybackInteractorProtocol {
faible var présentateur : VPKVideoPlaybackInteractorOutputProtocol ?
func playbackManager(_: VPKVideoPlaybackManager, didPrepareplayerLayer: AVPlayerLayer) {presenter?.onVideoLoadSuccess(playerLayer)
}
Les PROS
LES CONS
RECOMMANDATION
Nous recommandons d’utiliser une forme de VIPER si :
- Votre équipe est ouverte à l’apprentissage d’un nouveau pattern
- Votre équipe dispose d’au moins 1 semaine pour l’apprendre, le comprendre et l’expérimenter
- Plus facile de soutenir une approche pilotée par les tests en raison de la nature découplée du pattern
- Vous êtes enthousiaste à son sujet 🙂
- Les membres de l’équipe sont responsables de différents modules
Nous ne recommandons pas d’utiliser une forme de VIPER si :
- Votre équipe est réactive, et les fonctionnalités changent sur une base presque quotidienne
- Vous construisez une petite application qui est une expérience et qui est utilisée pour tester un problème
- Vous ne vous souciez pas des tests
VIPER brillera avec des projets à long terme, des applications où la sécurité et la logique métier sont élevées, et lorsque les tests doivent être mis en œuvre.
Vous remarquerez que votre code devient plus propre. Vous serez en mesure de séparer les fonctionnalités & modules entre les membres de l’équipe facilement. Lorsqu’il y a un bug de formatage de l’interface utilisateur, vous saurez instantanément qu’il faut vérifier la classe Presenter. Lorsqu’il y a un bug logique, vous savez qu’il provient de l’interactor. Plus de clarté, moins de venin.
INFOS SUPPLÉMENTAIRES
Ce sujet a été présenté à try ! Swift NYC 2017. La présentation peut être trouvée ici.
Tous les exposés supplémentaires de try ! Swift peuvent être trouvées ici.