VIPER es complejo, aterrador y, bueno, diferente. Hay una pizca de veneno bajo la superficie de VIPER y puede doler. Sin embargo, con el tiempo el dolor disminuye, y se convierte en una de las curas para estructurar su código. Adiós al veneno. Hola felicidad.
Esto es lo que los desarrolladores de iOS pueden relacionar:
El patrón de diseño VIPER es una fuente de confusión &malentendida para muchos desarrolladores. Los pros & contras y el uso práctico de este patrón no están claros. Después de trabajar en aplicaciones con los patrones MVC, MVVM, MVVM + RxSwift, quise conocer a fondo este patrón de diseño que suena vicioso y ver de qué se trataba el rumor (¿silbido?). Como resultado, decidí probar la siguiente hipótesis:
Como desarrollador me gustaría utilizar el patrón de diseño VIPER para construir módulos reutilizables.
Escribimos este post con la esperanza de que te permita evaluar si VIPER es o no la opción adecuada para tu proyecto o framework.
VIPER
Algunos compañeros de Mutual Mobile querían mejorar la cobertura de las pruebas de sus aplicaciones para iOS. Como resultado, se les ocurrió el patrón en sí. Más en lo que respecta a la procedencia de la misma se puede encontrar aquí.
Literalmente hablando, Viper es un backronym que significa View, Interactor, Presenter, Entity, Router.
Teóricamente hablando, al utilizar Viper tu aplicación o framework se dividiría en «módulos» VIPER – componentes de módulos reutilizables &. En forma de diagrama se traduce en esto:
Vista: La capa de la interfaz establece la vista, actualiza las imágenes y las etiquetas, pasa la entrada del usuario al presentador
Interactor: Lógica de negocio, manipulación de datos, transformación de modelos, interactúa con los servicios de la API &clases de gestión de datos. Pasa la salida al presentador
Presentador: Formatea los datos recibidos del interactor para la vista, recibe la entrada de la vista y la pasa al interactor
Entidad: Un dato simple que representa lo necesario para que el Módulo VIPER funcione
Router: Responsable de la navegación del módulo y de la navegación de la aplicación
VIPER en código no funciona en la misma secuencia que las letras que se leen. En otras palabras… las acciones en su código fluirán más bien en el orden siguiente:
Interactor → Router , Entidad, & Externos → Presentador → Vista
VIPER se convierte más bien en IREPV… Eso es una palabra para alguien, en algún lugar.
EXPERIMENTO
Validamos la hipótesis de utilizar VIPER en un módulo reutilizable construyendo un framework. El marco se centra en la reproducción de contenidos de vídeo. La reproducción de vídeo implica actualizaciones de la UI, descarga de datos & sincronización de datos. Estas interacciones demostraron que un módulo de reproducción de vídeo sería un candidato digno de un módulo estructurado VIPER.
OVERVIEW
Los módulos VIPER están orientados al protocolo. Cada función, propiedad, entrada y salida se implementa a través de un protocolo.
Como resultado utilizamos la idea de un:
Contrato: un archivo swift que describe todos los protocolos utilizados en el módulo VIPER. Es similar a un archivo de cabecera Objective-C. Fragmento de código siguiente:
//*** 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: ¿Cadena? { get set }
var playbackManager: (VPKVideoPlaybackManagerInputProtocol &VPKVideoPlaybackManagerOutputProtocol &VPKVideoPlaybackManagerProtocol)? { get set }
// PRESENTADOR -> 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()}
También queríamos que los desarrolladores de este framework tuvieran una forma fácil de configurar los módulos VIPER.
Como resultado utilizamos la idea de un:
Builder: Un objeto simple utilizado para configurar el módulo VIPER y establecer dependencias. Instancia el presentador y el interactor. Finalmente devuelve 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))
}
También utilizamos un protocolo para configurar todas las dependencias del módulo:
class VPKDependencyManager: VPKDependencyManagerProtocol {
static func videoView(with interactor:VPKVideoPlaybackInteractorProtocolo &VPKVideoPlaybackInteractorInputProtocolo, y presentador:VPKVideoPlaybackPresenterProtocolo &VKVideoPlaybackInteractorOutputProtocolo) -> 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
}
EL MARCO: VIDEOPLAYBACKKIT
Consideremos la historia de usuario:
**Reproducir un vídeo **
Usando AVFOUNDATION sabemos lo siguiente:
- Debe usar AVPlayer
- AVPlayer le dará un AVPlayerLayer para agregar a su vista
- AVPlayer toma tiempo para preparar -. particularmente con urls de vídeo remotas
- Los datos de vídeo remotos necesitan tiempo para descargarse
-
Dos acciones primarias de la vista
- Reproducir
- Pausar
-
Resultados de estas acciones:
- El AVPlayer recibe la url del vídeo para descargar los datos para
- Cuando está listo, el AVPlayer devuelve un AVPlayerLayer
- El AVPlayerLayer debe añadirse a una vista
- En pausa, el AVPlayer debe detener la reproducción
**IREPV (AKA VIPER) & AVKit, AVFoundation **
ENTRADAS Y SALIDAS
Ahora bien, esto es clave para entender el patrón, calmando tu cerebro, evitando la caída en complejidades de conejo.
Lección principal 1: entender las entradas y las salidas de tu módulo.
Vista
- Entrada: didTapPlay
- Salida: Presentador didTapPlay
Presentador
- Input: didTapPlay recibe de la vista
- Output: onPlayerLayerSuccess(playerLayer) – emite esto al presentador
Interactor
- Input: didTapPlay – le dice al gestor externo
-
Output: onVideoPlayerLayerSuccess – pasa la capa del reproductor al presentador
protocolo 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 vez que la vista recibe el AVPlayerLayer del presentador, puede recargar su interfaz.
protocol VPKVideoViewProtocol: class {func reloadInterface(with playerLayer: AVPlayerLayer)}
Relación entre el Interactor y el Presentador
Las relaciones entre las piezas de VIPER son diferentes a las de otros patrones de iOS. Cada objeto se aferra a los demás (débilmente, por supuesto). Puedes pensar que uno es el delegado del otro. Vea el ejemplo siguiente:
-
El Presentador mantiene una referencia débil al interactor. El presentador SOLO se preocupa por el protocolo de entrada del interactor. El reenvío de la entrada «didTapPlay».
protocolo VPKVideoPlaybackPresenterProtocolo: clase {var interactor: VPKVideoPlaybackInteractorInputProtocol? { get set }//debil}
-
El Presentador reenvía el mensaje de entrada; sin embargo, el Presentador también depende del conocimiento de los interactores de los datos de la entidad de vídeo.
public class VPKVideoPlaybackPresenter:VPKVideoPlaybackPresenterProtocol {func didTapVideoView() {guard let safeVideoUrl = interactor?.videoType.videoUrl else {return }interactor?.didTapVideo(videoURL: safeVideoUrl, at:self.indexPath)}}
CONFORMIDAD CON EL PROTOCOLO
La relación entre el presentador y el interactor es la parte más importante del flujo de datos.
El Presentador se preocupa por la Salida de los Interactores
El Interactor se preocupa por la Entrada de los Presentadores
Ver más abajo:
El presentador se ajusta al protocolo de salida del interactor.
//MARK: INTERACTOR --> Presenterextension VPKVideoPlaybackPresenter:VPKVideoPlaybackInteractorOutputProtocol { func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer) {videoView?.reloadInterface(with: playerLayer)
}}
El Interactor utiliza la propiedad débil del presentador para devolver los datos de salida.
public class VPKVideoPlaybackInteractor:VPKVideoPlaybackInteractorProtocol {
débil var presentador: VPKVideoPlaybackInteractorOutputProtocol?
func playbackManager(_: VPKVideoPlaybackManager, didPrepareplayerLayer: AVPlayerLayer) {presenter?.onVideoLoadSuccess(playerLayer)
}
LOS PROS
Los contras
RECOMENDACIÓN
Recomendamos utilizar una forma de VIPER si:
- Su equipo está abierto a aprender un nuevo patrón
- Su equipo tiene al menos 1 semana para aprender, entender y experimentar con él
- Es más fácil soportar un enfoque dirigido por pruebas debido a la naturaleza desacoplada del patrón
- Está entusiasmado con él 🙂
- Los miembros del equipo son responsables de diferentes módulos
No recomendamos utilizar una forma de VIPER si:
- Su equipo es reactivo, y las características cambian casi a diario
- Está construyendo una pequeña aplicación que es un experimento y que se utiliza para probar un problema
- No le importan las pruebas
VIPER brillará con proyectos a largo plazo, aplicaciones donde la seguridad y la lógica de negocio son altas, y cuando las pruebas deben ser implementadas.
Notarás que tu código se vuelve más limpio. Usted será capaz de separar las características & módulos entre los miembros del equipo fácilmente. Cuando hay un error de formato de la interfaz de usuario, usted sabrá al instante para comprobar la clase Presentador. Cuando hay un error lógico, sabes que proviene del interactor. ¡Más claridad, menos veneno.
INFORMACIÓN ADICIONAL
Este tema fue presentado en try! Swift NYC 2017. La presentación se puede encontrar aquí. ¡
Todas las charlas adicionales de try! Swift se pueden encontrar aquí.