VIPER jest złożony, przerażający i cóż – inny. Pod powierzchnią VIPERa kryje się odrobina jadu i może to boleć. Jednak z czasem ból ustępuje i staje się on jednym z lekarstw na strukturyzację kodu. Bye bye venom. Witaj szczęście.
Oto, do czego mogą się odnieść programiści iOS:
Wzorzec projektowy VIPER jest źródłem zamieszania &nieporozumień dla wielu programistów. Plusy & minusy i praktyczne zastosowanie tego wzorca są niejasne. Po pracy nad aplikacjami z wzorcami MVC, MVVM, MVVM + RxSwift, chciałem dogłębnie zrozumieć ten złośliwie brzmiący wzorzec projektowy i zobaczyć o co tyle szumu (syczenia?). W związku z tym postanowiłem przetestować następującą hipotezę:
Jako programista chciałbym używać wzorca projektowego VIPER do budowania modułów wielokrotnego użytku.
Napisaliśmy ten post z nadzieją, że pozwoli ci on ocenić, czy VIPER jest właściwą opcją dla twojego projektu lub frameworka.
VIPER
Kilku ludzi z Mutual Mobile chciało poprawić zasięg swoich testów w aplikacjach na iOS. W rezultacie, wymyślili sam wzorzec. Więcej na temat tego skąd się wziął można znaleźć tutaj.
Dosłownie rzecz ujmując, Viper jest skrótem od View, Interactor, Presenter, Entity, Router.
Teoretycznie rzecz ujmując, podczas używania Vipera Twoja aplikacja lub framework będą podzielone na „moduły” VIPER – wielokrotnego użytku & komponenty modułu. W formie diagramu przekłada się to na to:
Widok: Warstwa interfejsu układa widok, aktualizuje obrazy i etykiety, przekazuje dane wejściowe użytkownika do prezentera
Interaktor: Logika biznesowa, manipulowanie danymi, przekształcanie modeli, współdziała z klasami menedżera danych API Services &. Przekazuje dane wyjściowe do prezentera
Prezenter: Formatyzuje dane otrzymane od interaktora dla widoku, odbiera dane wejściowe z widoku i przekazuje je do interaktora
Entity: Prosta dana reprezentująca to, co jest potrzebne do funkcjonowania modułu VIPER
Router: Odpowiedzialny za nawigację po module i nawigację po aplikacji
VIPER w kodzie nie działa w takiej samej kolejności jak czytane litery. Innymi słowy… działania w twoim kodzie będą przebiegać bardziej w poniższej kolejności:
Interactor → Router , Entity, & Externals → Presenter → View
VIPER staje się bardziej jak IREPV… To jest słowo do kogoś, gdzieś.
EXPERIMENT
Weryfikowaliśmy hipotezę użycia VIPER w module wielokrotnego użytku poprzez zbudowanie frameworka. Szkielet jest skoncentrowany na odtwarzaniu zawartości wideo. Odtwarzanie wideo wiąże się z aktualizacjami UI, pobieraniem danych &synchronizacją danych. Te interakcje dowiodły, że moduł odtwarzania wideo byłby wartościowym kandydatem na moduł strukturalny VIPER.
OVERVIEW
Moduły VIPER są zorientowane na protokół. Każda funkcja, właściwość, wejście i wyjście jest zaimplementowane poprzez protokół.
W rezultacie użyliśmy idei a:
Kontrakt: plik typu swift opisujący wszystkie protokoły używane w module VIPER. Jest on podobny do pliku nagłówkowego w języku objective-C. Code snippet below:
//*** 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 &VKVideoPlaybackManagerOutputProtocol &VKVideoPlaybackManagerProtocol)? { 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()}
Chcieliśmy również, aby programiści tego frameworka mieli łatwy sposób konfigurowania modułów VIPER.
W rezultacie wykorzystaliśmy ideę:
Buildera: Prosty obiekt używany do konfigurowania modułu VIPER i ustawiania zależności. Instancjonuje on prezentera i interaktora. Ostatecznie zwraca widok do kontrolera viewcontroller.
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))
}}
Użyliśmy również protokołu do ustawienia wszystkich zależności dla modułu:
class VPKDependencyManager: VPKDependencyManagerProtocol {
static func videoView(with interactor:VPKVideoPlaybackInteractorProtocol &VPKVideoPlaybackInteractorInputProtocol, and 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
}}
THE FRAMEWORK: VIDEOPLAYBACKKIT
Zastanówmy się nad historią użytkownika:
**PLAY A VIDEO **
Używając AVFOUNDATION wiemy co następuje:
- Musi użyć AVPlayer
- AVPlayer da ci AVPlayerLayer do dodania do twojego widoku
- AVPlayer zajmuje czas na przygotowanie – np. szczególnie w przypadku zdalnych urli wideo
- Dane zdalnego wideo potrzebują czasu na pobranie
-
Dwie podstawowe akcje widoku
- Odtwórz
- Pauza
-
Wyniki tych akcji:
- AVPlayer otrzymuje adres url wideo, aby pobrać dane dla
- Gdy jest gotowy, AVPlayer zwraca AVPlayerLayer
- AvPlayerLayer musi zostać dodany do widoku
- Przy pauzie, AVPlayer musi zatrzymać odtwarzanie
- Input: didTapPlay
- Output: Presenter didTapPlay
- Input: didTapPlay otrzymuje z widoku
- Output: onPlayerLayerSuccess(playerLayer) – wyprowadza to do presentera
- Input: didTapPlay – informuje o tym zewnętrznego menedżera
-
Output: onVideoPlayerLayerSuccess – przekazuje warstwę odtwarzacza do prezentera
protokół VPKVideoPlaybackInteractorOutputProtocol: class {
**IREPV (AKA VIPER) & AVKit, AVFoundation **
INPUTS AND OUTPUTS
Teraz jest to klucz do zrozumienia wzorca, uspokojenia mózgu, uniknięcia wpadnięcia w zawiłości króliczej nory.
Główna lekcja 1: Zrozum wejścia i wyjścia twojego modułu.
View
Presenter
Interactor
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)}
Gdy widok otrzyma AVPlayerLayer od prezentera, może przeładować swój interfejs.
protocol VPKVideoViewProtocol: class {func reloadInterface(with playerLayer: AVPlayerLayer)}
ZWIĄZKI WEWNĘTRZNE I RELACJE Z PREZENTEREM
Zależności między elementami VIPER są inne niż w innych wzorcach iOS. Każdy obiekt trzyma się siebie nawzajem (oczywiście słabo). Możesz myśleć o tym, że jeden jest delegatem dla drugiego. Zobacz poniższy przykład:
-
Prezenter trzyma słabe odniesienie do interaktora. Prezenter TYLKO troszczy się o protokół wejściowy interaktora. Przekazywanie danych wejściowych „didTapPlay”.
protokół VPKVideoPlaybackPresenterProtocol: class {var interactor: VPKVideoPlaybackInteractorInputProtocol? {get set }//weak}
-
Prezenter przekazuje dalej komunikat wejściowy; jednakże, prezenter zależy również od wiedzy interactorów na temat danych encji wideo.
public class VPKVideoPlaybackPresenter:VPKVideoPlaybackPresenterProtocol {func didTapVideoView() {guard let safeVideoUrl = interactor?.videoType.videoUrl else {return }interactor?.didTapVideo(videoURL: safeVideoUrl, at:self.indexPath)}}
PROTOCOL CONFORMANCE
Związek prezentera i interaktora jest najważniejszą częścią przepływu danych.
Prezenter dba o Wyjście Interaktorów
Interaktor dba o Wejście Prezenterów
Patrz poniżej:
Prezenter jest zgodny z protokołem wyjściowym interaktora.
//MARK: INTERACTOR --> Presenterextension VPKVideoPlaybackPresenter:VPKVideoPlaybackInteractorOutputProtocol { func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer) {videoView?.reloadInterface(with: playerLayer)
}}
Interaktor używa słabej właściwości prezentera do przekazania z powrotem danych wyjściowych.
public class VPKVideoPlaybackInteractor:VPKVideoPlaybackInteractorProtocol {
weak var presenter: VPKVideoPlaybackInteractorOutputProtocol?
func playbackManager(_: VPKVideoPlaybackManager, didPrepareplayerLayer: AVPlayerLayer) {presenter?.onVideoLoadSuccess(playerLayer)
}}
THE PROS
THE CONS
RECOMMENDATION
Zalecamy stosowanie formy VIPER jeśli:
- Twój zespół jest otwarty na naukę nowego wzorca
- Twój zespół ma co najmniej 1 tydzień na poznanie, zrozumienie i eksperymentowanie z nim
- Łatwiejsze wspieranie podejścia opartego na sterowaniu testami ze względu na odsprzężoną naturę wzorca
- Jesteś podekscytowany tym 🙂
- Członkowie zespołu są odpowiedzialni za różne moduły
Nie zalecamy używania formy VIPER, jeśli:
- Twój zespół jest reaktywny, a funkcje zmieniają się niemal codziennie
- Budujesz małą aplikację, która jest eksperymentem i jest używana do testowania problemu
- Nie zależy Ci na testach
VIPER zabłyśnie przy długoterminowych projektach, aplikacjach, w których bezpieczeństwo i logika biznesowa są na wysokim poziomie, oraz gdy testowanie musi być wdrożone.
Zauważysz, że twój kod stanie się czystszy. Będziesz w stanie łatwo rozdzielić funkcje & modułów wśród członków zespołu. Gdy pojawi się błąd formatowania UI, natychmiast będziesz wiedział, aby sprawdzić klasę Presenter. Kiedy pojawi się błąd logiczny, będziesz wiedział, że pochodzi on z interaktora. Więcej jasności, mniej jadu.
ADDITIONAL INFO
Ten temat został zaprezentowany na try! Swift NYC 2017. Prezentację można znaleźć tutaj.
Wszystkie dodatkowe prelekcje try! Swift można znaleźć tutaj.