VIPER é complexo, assustador, e bem – diferente. Há uma pitada de veneno por baixo da superfície do VIPER e pode doer. No entanto, com o tempo a dor diminui, e torna-se uma das curas para estruturar o seu código. Adeus, adeus veneno. Olá felicidade.
Aqui está o que os programadores do iOS podem relacionar:
O padrão de design VIPER é uma fonte de confusão & mal-entendido para muitos desenvolvedores. Os prós & contras e o uso prático deste padrão não são claros. Depois de trabalhar em aplicações com os padrões MVC, MVVM, MVVM + RxSwift, eu queria ter uma compreensão profunda deste padrão de design de som vicioso e ver o que era o zumbido (hiss?). Como resultado, eu decidi testar a seguinte hipótese:
Como desenvolvedor eu gostaria de usar o padrão de design VIPER para construir módulos reutilizáveis.
Escrevemos este post na esperança de que ele lhe permita avaliar se VIPER é ou não a opção certa para seu projeto ou estrutura.
VIPER
Uma poucas pessoas na Mutual Mobile queriam melhorar sua cobertura de testes em seus aplicativos iOS. Como resultado, eles criaram o padrão em si. Mais sobre de onde ele veio pode ser encontrado aqui.
Literalmente falando, Viper é um backronym que representa View, Interactor, Presenter, Entity, Router.
Teóricamente falando, enquanto usando Viper seu aplicativo ou framework seria dividido em “módulos” VIPER – reutilizáveis & componentes de módulo. Em forma de diagrama ele se traduz para isto:
Ver: A camada de interface estabelece a visualização, actualiza imagens e etiquetas, passa a entrada do utilizador para o apresentador
Interactor: Lógica de negócio, manipulação de dados, transformação de modelos, interage com as classes API Services & data manager. Passa a saída para o apresentador
Apresentador: Formata os dados recebidos do interator para a visualização, recebe a entrada da visualização e a passa para o interator
Entity: Um simples dado representando o que é necessário para o Módulo VIPER funcionar
Roteador: Responsável pela navegação do módulo e da aplicação de navegação
VIPER em código não funciona na mesma sequência que as letras que lê. Em outras palavras… as ações em seu código irão fluir mais na ordem abaixo:
Interactor → Router , Entidade, & Externals → Presenter → View
VIPER torna-se mais como IREPV… Isso é uma palavra para alguém, em algum lugar.
EXPERIMENTO
Validamos a hipótese de usar VIPER em um módulo reutilizável através da construção de um framework. O framework é focado na reprodução de conteúdo de vídeo. A reprodução de vídeo envolve atualizações de IU, download de dados & sincronização de dados. Estas interações provaram que um módulo de reprodução de vídeo seria um candidato válido para um módulo estruturado VIPER.
OVERVIEW
MódulosVIPER são orientados ao protocolo. Cada função, propriedade, entrada e saída é implementada através de um protocolo.
Como resultado usamos a ideia de um:
Contrato: um arquivo rápido descrevendo todos os protocolos usados no módulo VIPER. É semelhante a um ficheiro de cabeçalho objective-C. Código snippet abaixo:
//*** Interactor//*//*protocol VPKVideoPlaybackInteractorProtocol: class {
init(entidade: VPKVideoType)}
protocol VPKVideoPlaybackInteractorInputProtocol: class {
var apresentador: VPKVideoPlaybackInteractorOutputProtocolo? { get set }var videoType: VPKVideoType { get set }var remoteImageURL: URL? { get set }var localImageName: String? { get set }
var playbackManager: (VPKVideoPlaybackManagerInputProtocol &VPKVideoPlaybackManagerOutputProtocol &VPKVideoPlaybackManagerProtocol)? { get set }
// APRESENTADOR -> INTERACTORfunc didTapVideo(videoURL: URL, em indexPath: NSIndexPath?)func didScrubTo(_ timeInSeconds: TimeInterval)func didSkipBack(_ seconds: Float)func didSkipForward(_ seconds: Float)func didToggleViewExpansion()func didMoveOffScreen()func didReuseInCell()}
Nós também queríamos que os desenvolvedores deste framework tivessem uma maneira fácil de configurar módulos VIPER.
Como resultado usamos a ideia de a:
Builder: Um objecto simples usado para configurar o módulo VIPER e configurar as dependências. Instanta o apresentador e o interautor. Finalmente ele retorna a visualização para o viewcontroller.
public class VPKVideoPlaybackBuilder: VPKVideoPlaybackBuilderProtocol {
//Feedpublic static func vpk_buildViewInCell(para videoType:VPKVideoType, em indexPath: NSIndexPath, com playbackBarTheme:ToolBarTheme = .normal, vista de conclusãoCompletion:VideoViewClosure) {
let interactor = VPKVideoPlaybackInteractor(entity:videoType)let presenter: VPKVideoPlaybackPresenterProtocol &VPKVideoPlaybackInteractorOutputProtocol =VPKVideoPlaybackPresenter(with: false, showInCell: nil,playbackTheme: playbackBarTheme)viewCompletion(VPKDependencyManager.videoView(with:interactor, and: presenter))
}}
Também usamos um protocolo para configurar todas as dependências para o módulo:
class VPKDependencyManager: VPKDependencyManagerProtocol {
static func videoView(with interactor:VPKVideoPlaybackInteractorProtocolo&VPKVideoPlaybackInteractorInputProtocolo, e apresentador:VPKVideoPlaybackProtocolo Apresentador&VPKVideoPlaybackInteractorOutputProtocolo) -> 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
}}
O QUADRO-QUADRO: VIDEOPLAYBACKIT
Vejamos a história do utilizador:
>**Jogue um vídeo **
Utilizando a AVFOUNDATION sabemos o seguinte:
- Tem de usar o AVPlayer
- AVPlayer dá-lhe um AVPlayerLayer para adicionar à sua visualização
- AVPlayer leva tempo a preparar – particularmente com urls de vídeo remoto
- Dados de vídeo remoto precisam de tempo para descarregar
-
Duas acções de visualização primária
- Tocar
- Pausa
-
Resultados destas acções:
- AVPlayer recebe url de vídeo para baixar dados para
- Quando pronto, o AVPlayer retorna um AVPlayerLayer
- O AVPlayerLayer deve ser adicionado a uma visualização
- Em pausa, o AVPlayer deve parar a reprodução
**IREPV (AKA VIPER) & AVKit, AVFoundation **
INPUTS AND OUTPUTS
Agora esta é a chave para entender o padrão, acalmando o cérebro, evitando a queda em complexidades do coelho.
Lição Principal 1: Entenda as entradas e as saídas do seu módulo.
Ver
- Input: didTapPlay
- Output: Apresentador didTapPlay
Apresentador
- Entrada: didTapPlay recebe da vista
- Saída: onPlayerLayerSuccess(playerLayer) – envia isto ao apresentador
Interactor
- Entrada: didTapPlay – diz ao gestor externo
-
Output: onVideoPlayerLayerSuccess – passa a camada de jogador ao apresentador
protocolo VPKVideoPlaybackInteractorOutputProtocol: classe {
var progressTime: TimeInterval { get set }
/INTERACTOR –> PRESENTERfunc onVideoResetPresentation()func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer)func onVideoLoadFail(_ erro: String)func onVideoDidStartPlayingWith(_ duração: TimeInterval)func onVideoDidStopPlaying()func onVideoDidPlayToEnd()func onVideoPlayingFor(_ seconds: TimeInterval)}
Após a visualização receber o AVPlayerLayer do apresentador, ele pode recarregar a sua interface.
protocol VPKVideoViewProtocol: class {func reloadInterface(with playerLayer: AVPlayerLayer)}
RELACIONAMENTO DO INTERACTOR E DO APRESENTADOR
As relações entre as peças do VIPER são diferentes daquelas de outros padrões iOS. Cada objecto agarra-se um ao outro (fracamente, claro). Você pode pensar nele como um sendo o delegado do outro. Veja o exemplo abaixo:
-
O Apresentador tem uma fraca referência ao interactivo. O apresentador se preocupa APENAS com o protocolo de entrada do interacionista. O encaminhamento do input “didTapPlay”.
protocolo VPKVideoPlaybackProtocolo do Apresentador: classe {var interacionador: VPKVideoPlaybackInteractorInputProtocol? { get set }//weak}
-
The Presenter forward the input message; however, the Presenter also depends on the interactors knowledge of the video entity data.
public class VPKVideoPlaybackPresenter:VPKVideoPlaybackPresenterProtocol {func didTapVideoView() {guard let safeVideoUrl = interactor?.videoType.videoUrl else {return }interactor?.didTapVideo(videoURL: safeVideoUrl, at:self.indexPath)}}}
PROTOCOL CONFORMANCE
A relação apresentador e interautor é a parte mais importante do fluxo de dados.
Apresentador preocupa-se com a saída dos Interactores
O Interactor preocupa-se com a entrada dos Apresentadores
Ver abaixo:
O apresentador está em conformidade com o protocolo de saída do Interactor.
//MARK: INTERACTOR --> Presenterextension VPKVideoPlaybackPresenter:VPKVideoPlaybackInteractorOutputProtocol { func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer) {videoView?.reloadInterface(with: playerLayer)
}}
O Interactor usa a propriedade fraca do apresentador para passar os dados de saída.
public class VPKVideoPlaybackInteractor:VPKVideoPlaybackInteractorProtocol {
Apresentador de var fraco: VPKVideoPlaybackInteractorOutputProtocol?
func playbackManager(_: VPKVideoPlaybackManager, didPrepareplayerLayer: AVPlayerLayer) {presenter?.onVideoLoadSuccess(playerLayer)
}}
THE PROS
O CONS
RECOMENDAÇÃO
Recomendamos o uso de um formulário de VIPER se:
- A tua equipa está aberta a aprender um novo padrão
- A tua equipa tem pelo menos 1 semana para aprender, compreender e experimentar
- A tua equipa tem pelo menos 1 semana para aprender, compreender e experimentar
- A tua equipa está aberta a aprender um novo padrão
- Estás entusiasmado com isso 🙂
- Os membros da equipe são responsáveis por diferentes módulos
Não recomendamos o uso de uma forma de VIPER se:
- Sua equipe é reativa, e as características mudam quase diariamente
- Você está construindo uma pequena aplicação que é uma experiência e está sendo usada para testar um problema
- Você não se importa com testes
VIPER brilhará com projetos de longo prazo, aplicações onde a segurança e a lógica de negócios são altas, e quando os testes devem ser implementados.
Vai notar que o seu código se tornará mais limpo. Você será capaz de separar facilmente os recursos & módulos entre os membros da equipe. Quando houver um bug de formatação de IU você saberá instantaneamente para verificar a classe Presenter. Quando há um bug lógico, você sabe que ele é decorrente do interautor. Mais clareza, menos veneno.
INFORMAÇÃO ADICIONAL
Este tópico foi apresentado na tentativa! Swift NYC 2017. A apresentação pode ser encontrada aqui.
Tentativa adicional! As palestras Swift podem ser encontradas aqui.