VIPER je složitý, děsivý a jiný. Pod povrchem VIPERu se skrývá náznak jedu, který může bolet. Časem však bolest ustoupí a stane se jedním z léků na strukturování kódu. Sbohem, jede. Ahoj štěstí.
Tady je to, s čím se mohou vývojáři iOS ztotožnit:
Návrhový vzor VIPER je pro mnoho vývojářů zdrojem & nepochopení. Výhody & nevýhody a praktické využití tohoto vzoru jsou nejasné. Po práci na aplikacích se vzory MVC, MVVM, MVVM + RxSwift jsem chtěl tento zlomyslně znějící návrhový vzor pochopit do hloubky a zjistit, co je to za povyk (sykot?). V důsledku toho jsem se rozhodl otestovat následující hypotézu:
Jako vývojář bych rád používal návrhový vzor VIPER k vytváření opakovaně použitelných modulů.
Napsal jsem tento příspěvek v naději, že vám umožní posoudit, zda je VIPER pro váš projekt nebo framework tou správnou volbou.
VIPER
Několik lidí ze společnosti Mutual Mobile chtělo zlepšit pokrytí testováním svých aplikací pro iOS. V důsledku toho přišli se samotným vzorem. Více v souvislosti s tím, odkud pochází, najdete zde.
Doslova řečeno, Viper je zkratka znamenající View, Interactor, Presenter, Entity, Router.
Teoreticky řečeno, při použití Viper byste aplikaci nebo framework rozdělili na „moduly“ VIPER – opakovaně použitelné & komponenty modulu. Ve formě diagramu se to dá přeložit takto:
Pohled: Vrstva rozhraní rozvrhuje zobrazení, aktualizuje obrázky a popisky, předává vstupy od uživatele prezentátorovi
Interaktor: Obchodní logika, manipulace s daty, transformace modelů, interakce se službami API &třídy správce dat. Předává výstup prezentéru
Presenter: Formátuje data přijatá od interaktoru pro zobrazení, přijímá vstupy ze zobrazení a předává je interaktoru
Entita: Jednoduchá data představující to, co je potřeba pro fungování modulu VIPER
Router: Zodpovídá za navigaci po modulu a aplikaci
VIPER v kódu nefunguje ve stejném pořadí jako písmena, která čtete. Jinými slovy… akce ve vašem kódu budou probíhat spíše v níže uvedeném pořadí:
Interaktor → Router , Entita, & Externals → Presenter → View
VIPER se stane spíše IREPV… To je slovo pro někoho, někde.
EXPERIMENT
Hypotézu o použití VIPERu v opakovaně použitelném modulu jsme ověřili sestavením rámce. Framework je zaměřen na přehrávání videoobsahu. Přehrávání videa zahrnuje aktualizace uživatelského rozhraní, stahování dat & synchronizaci dat. Tyto interakce prokázaly, že modul pro přehrávání videa by byl vhodným kandidátem na strukturovaný modul VIPER.
PŘEHLED
Moduly VIPER jsou protokolově orientované. Každá funkce, vlastnost, vstup a výstup jsou implementovány prostřednictvím protokolu.
V důsledku toho jsme použili myšlenku tzv:
Kontrakt: soubor swift popisující všechny protokoly použité v modulu VIPER. Je podobný hlavičkovému souboru Objective-C. Úryvek kódu níže:
//*** Interactor//*//*protocol VPKVideoPlaybackInteractorProtocol: class {
init(entita: 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()}
Chtěli jsme také, aby vývojáři tohoto frameworku měli snadný způsob konfigurace modulů VIPER.
V důsledku toho jsme použili myšlenku:
Builder: Jednoduchý objekt sloužící ke konfiguraci modulu VIPER a nastavení závislostí. Instancuje prezentér a interaktor. Nakonec vrací pohled do viewcontrolleru.
public class VPKVideoPlaybackBuilder: VPKVideoPlaybackBuilderProtocol {
//Feedpublic static func vpk_buildViewInCell(for videoType:VPKVideoType, at indexPath: NSIndexPath, with 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))
}}
Pomocí protokolu jsme také nastavili všechny závislosti modulu:
class VPKDependencyManager: VPKDependencyManagerProtocol {
static func videoView(with interactor:VPKVideoPlaybackInteractorProtocol &VPKVideoPlaybackInteractorInputProtocol a 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
}}
RÁMEC: VIDEOPLAYBACKKIT
Uvažujme uživatelský příběh:
**Přehrát VIDEO **
Pomocí AVFOUNDATION víme následující:
- Musí použít AVPlayer
- AVPlayer vám poskytne AVPlayerLayer, který přidáte do zobrazení
- AVPlayer potřebuje čas na přípravu -. zejména u vzdálených videoukázek
- Stažení vzdálených videodat vyžaduje čas
-
Dvě základní akce zobrazení
- Přehrát
- Pauza
-
Výsledky těchto akcí:
- AVPlayer obdrží url adresu videa pro stažení dat
- Když je připraven, AVPlayer vrátí AVPlayerLayer
- AVPlayerLayer musí být přidán do zobrazení
- Při pozastavení musí AVPlayer zastavit přehrávání
**IREPV (AKA VIPER) & AVKit, AVFoundation **
Vstupy a výstupy
Nyní je to klíč k pochopení vzoru, zklidnění mozku, zabránění pádu do králičích nor složitostí.
Hlavní lekce 1: Pochopte vstupy a výstupy svého modulu.
Pohled
- Vstup: didTapPlay
- Výstup: Presenter didTapPlay
Presenter
- Input: didTapPlay obdrží od view
- Output: onPlayerLayerSuccess(playerLayer) – výstupy to presenter
Interactor
- Input: didTapPlay – sdělí externímu správci
-
Output: onVideoPlayerLayerSuccess – předá vrstvu přehrávače prezentéru
protokol VPKVideoPlaybackInteractorOutputProtocol: class {
var progressTime: TimeInterval { get set }
//INTERAKTOR –> PRESENTERfunc onVideoResetPresentation()func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer)func onVideoLoadFail(_ error: String)func onVideoDidStartPlayingWith(_ duration: timeInterval)func onVideoDidStopPlaying()func onVideoDidPlayToEnd()func onVideoPlayingFor(_ seconds: TimeInterval)}
Jakmile zobrazení obdrží AVPlayerLayer od prezentujícího, může znovu načíst své rozhraní.
protocol VPKVideoViewProtocol: class {func reloadInterface(with playerLayer: AVPlayerLayer)}
VZTAHY MEZI INTERAKTOREM A PREZENTÉREM
Vztahy mezi jednotlivými částmi VIPERu jsou jiné než u jiných vzorů iOS. Jednotlivé objekty na sebe vzájemně navazují (samozřejmě slabě). Můžete si to představit tak, že jeden je delegátem pro druhý. Viz příklad níže:
-
Prezentér drží slabý odkaz na interaktor. Prezentér se stará POUZE o vstupní protokol interaktoru. Předávání vstupu „didTapPlay“.
protokol VPKVideoPlaybackPresenterProtocol: třída {var interactor: VPKVideoPlaybackInteractorInputProtocol? { get set }//weak}
-
Presenter předává vstupní zprávu; Presenter je však také závislý na znalosti interaktorů o datech video entit.
public class VPKVideoPlaybackPresenter:VPKVideoPlaybackPresenterProtocol {func didTapVideoView() {guard let safeVideoUrl = interactor?.videoType.videoUrl else {return }interaktor?.didTapVideo(videoURL: safeVideoUrl, at:self.indexPath)}}
PROTOKOL CONFORMANCE
Vztah mezi prezentérem a interaktorem je nejdůležitější částí toku dat.
Prezentující se stará o výstupy interaktorů
Interaktor se stará o vstupy prezentujících
Podívejte se níže:
Prezentátor se řídí výstupním protokolem interaktoru.
//MARK: INTERACTOR --> Presenterextension VPKVideoPlaybackPresenter:VPKVideoPlaybackInteractorOutputProtocol { func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer) {videoView?.reloadInterface(with: playerLayer)
}}
Interaktor používá slabou vlastnost prezentátoru k předání výstupních dat zpět.
public class VPKVideoPlaybackInteractor:VPKVideoPlaybackInteractorProtocol {
weak var presenter: VPKVideoPlaybackInteractorOutputProtocol?
func playbackManager(_: VPKVideoPlaybackManager, didPrepareplayerLayer: AVPlayerLayer) {presenter?.onVideoLoadSuccess(playerLayer)
}}
PRO
VÝHODY
DOPORUČENÍ
Doporučujeme použít formu VIPER, pokud:
- Váš tým je otevřený učení nového vzoru
- Váš tým má alespoň 1 týden na jeho naučení, pochopení a experimentování s ním
- Snadněji podpoříte přístup řízený testy díky oddělené povaze vzoru
- Jste jím nadšeni 🙂
- Členové týmu jsou zodpovědní za různé moduly
Nedoporučujeme používat formu VIPER, pokud:
- Váš tým je reaktivní a funkce se mění téměř denně
- Vytváříte malou aplikaci, která je experimentem a slouží k testování problému
- Nezáleží vám na testech
VIPER zazáří u dlouhodobých projektů, aplikací, kde je vysoká bezpečnost a obchodní logika a kde je nutné provádět testování.
Zjistíte, že váš kód bude čistší. Budete moci snadno rozdělovat funkce &modulů mezi členy týmu. Při výskytu chyby ve formátování uživatelského rozhraní budete okamžitě vědět, že je třeba zkontrolovat třídu Presenter. Když se vyskytne logická chyba, budete vědět, že pramení z interaktoru. Více přehlednosti, méně jedu.
DOPLŇUJÍCÍ INFORMACE
Toto téma bylo prezentováno na konferenci Try! Swift NYC 2017. Prezentaci naleznete zde.
Všechny další informace o try! Swift naleznete zde.