A VIPER összetett, ijesztő és – nos – más. A VIPER felszíne alatt egy csipetnyi méreg van, és ez tud fájni. Idővel azonban a fájdalom alábbhagy, és a kód strukturálásának egyik gyógymódjává válik. Viszlát, méreg. Helló boldogság.
Ezzel az iOS-fejlesztők azonosulni tudnak:
A VIPER tervezési minta sok fejlesztő számára zavart & félreértés forrása. A minta előnyei & hátrányai és gyakorlati felhasználása nem egyértelmű. Miután az MVC, MVVM, MVVM + RxSwift mintákkal készült alkalmazásokon dolgoztam, szerettem volna mélyebb megértést szerezni erről az ördögi hangzású tervezési mintáról, és megnézni, mi ez a nagy felhajtás (sziszegés?). Ennek eredményeképpen úgy döntöttem, hogy tesztelem a következő hipotézist:
Fejlesztőként a VIPER tervezési mintát újrafelhasználható modulok létrehozására szeretném használni.
Ezt a bejegyzést abban a reményben írtuk, hogy képessé tesz téged annak értékelésére, hogy a VIPER a megfelelő választás-e a projekted vagy a keretrendszered számára.
VIPER
A Mutual Mobile-nál néhányan javítani akarták az iOS-alkalmazásaik tesztelési lefedettségét. Ennek eredményeképpen találták ki magát a mintát. További részletek arról, hogy honnan származik, itt találhatók.
A szó szoros értelmében a Viper a View, Interactor, Presenter, Entity, Router rövidítése.
A Viper használata során elméletileg az alkalmazás vagy a keretrendszer VIPER “modulokra” – újrafelhasználható & modul komponensekre – bomlik. Diagram formájában ez így fordítható le:
Nézet:
Interaktor: Az interfészréteg kiterjeszti a nézetet, frissíti a képeket és a címkéket, átadja a felhasználói bemenetet a prezentálónak
Interaktor: Üzleti logika, adatok manipulálása, modellek átalakítása, interakció az API szolgáltatásokkal & adatkezelő osztályokkal. Átadja a kimenetet a prezentálónak
Presenter: Az interaktortól kapott adatokat formázza a nézet számára, bemenetet kap a nézettől és továbbítja az interaktornak
Entity: Egyszerű adat, amely azt képviseli, ami a VIPER modul működéséhez szükséges
Router: A modul és az alkalmazás navigációjáért felelős
VIPER a kódban nem az olvasott betűk sorrendjében működik. Más szóval… a kódodban a műveletek inkább az alábbi sorrendben fognak folyni:
Interactor → Router , Entity, & Externals → Presenter → View
VIPER inkább IREPV lesz… Ez egy szó valakinek, valahol.
EXPERIMENT
Egy keretrendszer megépítésével igazoltuk a VIPER újrafelhasználható modulban való használatának hipotézisét. A keretrendszer a videotartalmak lejátszására összpontosít. A videó lejátszása magában foglalja a felhasználói felület frissítését, az adatok letöltését & az adatok szinkronizálását. Ezek a kölcsönhatások bebizonyították, hogy egy videólejátszó modul méltó jelölt lenne egy VIPER strukturált modul számára.
Áttekintés
A VIPER modulok protokollorientáltak. Minden funkció, tulajdonság, bemenet és kimenet egy protokollon keresztül valósul meg.
Ezért használtuk az ötletet:
Szerződés: egy gyors fájl, amely leírja a VIPER modulban használt összes protokollt. Ez hasonló egy objective-C fejlécfájlhoz. Az alábbi kódrészlet:
//*** 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 }
// 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()}
Azt is szerettük volna, hogy a keretrendszer fejlesztői könnyen konfigurálhassák a VIPER modulokat.
Ezért használtuk a:
Builder ötletét: Egy egyszerű objektum, amelyet a VIPER modul konfigurálására és a függőségek beállítására használunk. Ez instanciálja a prezentert és az interaktort. Végül visszaadja a nézetet a nézetvezérlőnek.
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))
}}}
A modul összes függőségének beállítására is használtunk egy protokollt:
class VPKDependencyManager: VPKDependencyManagerProtocol {
static func videoView(with interactor:VPKVideoPlaybackInteractorProtocol &VPKVideoPlaybackInteractorInteractorInputProtocol, és 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
Lássuk a felhasználói történetet:
**PLAY A VIDEO **
Az AVFOUNDATION használatával a következőket tudjuk:
- Must use AVPlayer
- AVPlayer will give you a AVPlayerLayer to add on your view
- AVPlayer takes time to prepare -. különösen távoli videó urlok esetén
- Távoli videó adatok letöltéséhez időre van szükség
-
Két elsődleges nézeti művelet
- Játszás
- Pauza
-
Ezek a műveletek eredményei:
- Az AVPlayer megkapja a videó url-jét az adatok letöltéséhez
- Ha kész, az AVPlayer visszaad egy AVPlayerLayert
- Az AVPlayerLayert hozzá kell adni egy nézethez
- A szünetben az AVPlayernek le kell állítania a lejátszást
**IREPV (AKA VIPER) & AVKit, AVFoundation **
INPUTS AND OUTPUTS
Most ez a kulcs a minta megértéséhez, az agyunk megnyugtatásához, a nyúlfarknyi bonyolultságba való beleesés elkerüléséhez.
Fő lecke 1: Értsd meg a modulod bemeneteit és kimeneteit.
View
- Input: didTapPlay
- Output: Presenter didTapPlay
Presenter
- Input: didTapPlay kapja a view
- Output: onPlayerLayerSuccess(playerLayer) – ezt adja ki a presenter
Interactor
- Input: DidTapPlay – közli a külső kezelővel
-
Output: onVideoPlayerLayerSuccess – átadja a lejátszóréteget a prezenternek
protokoll 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)}
Mihelyt a nézet megkapja az AVPlayerLayert a prezentertől, újra tudja tölteni a felületét.
protocol VPKVideoViewProtocol: class {func reloadInterface(with playerLayer: AVPlayerLayer)}
INTERAKTOR ÉS PRESENTER KAPCSOLATOK
A VIPER darabjai közötti kapcsolatok eltérnek más iOS mintákétól. Az egyes objektumok egymásba kapaszkodnak (természetesen gyengén). Úgy is elképzelhetjük, hogy az egyik a másik delegáltja. Lásd az alábbi példát:
-
A Presenter gyenge hivatkozást tart az interaktorra. A prezenter CSAK az interaktor bemeneti protokolljával törődik. A “didTapPlay” bemenet továbbítása.
protokoll VPKVideoPlaybackPresenterProtocol: class {var interactor: VPKVideoPlaybackInteractorInputProtocol? { get set }//weak}
-
A prezenter továbbítja a bemeneti üzenetet, azonban a prezenter függ attól is, hogy az interaktorok ismerik-e a videó entitás adatait.
public class VPKVideoPlaybackPresenter:VPKVideoPlaybackPresenterProtocol {func didTapVideoView() {guard let safeVideoUrl = interactor?.videoType.videoUrl else {return }interactor?.didTapVideo(videoURL: safeVideoUrl, at:self.indexPath)}}}
PROTOCOL CONFORMANCE
A prezenter és interaktor kapcsolata az adatfolyam legfontosabb része.
A Presenter törődik az Interactor kimenetével
Az Interactor törődik a Presenter bemenetével
Lásd alább:
A prezentátor megfelel az interaktor kimeneti protokolljának.
//MARK: INTERACTOR --> Presenterextension VPKVideoPlaybackPresenter:VPKVideoPlaybackInteractorOutputProtocol { func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer) {videoView?.reloadInterface(with: playerLayer)
}}}
Az interaktor a prezentátor gyenge tulajdonságát használja a kimeneti adatok visszaadására.
public class VPKVideoPlaybackInteractor:VPKVideoPlaybackInteractorProtocol {
weak var presenter: VPKVideoPlaybackInteractorOutputProtocol?
func playbackManager(_: VPKVideoPlaybackManager, didPrepareplayerLayer: AVPlayerLayer) {presenter?.onVideoLoadSuccess(playerLayer)
}}}
Az előnyök
A HÁTTÉR
JAVASLAT
A VIPER valamelyik formájának használatát javasoljuk, ha:
- A csapatod nyitott egy új minta megtanulására
- A csapatodnak legalább 1 hete van arra, hogy megtanulja, megértse és kísérletezzen vele
- Egyszerűbb támogatni a tesztvezérelt megközelítést a minta szétválasztott jellege miatt
- Elégedett vagy vele 🙂
- A csapattagok különböző modulokért felelősek
Nem javasoljuk a VIPER valamelyik formájának használatát, ha:
- A csapatod reaktív, és a funkciók szinte naponta változnak
- Egy kis alkalmazást építesz, ami egy kísérlet, és egy probléma tesztelésére használják
- Nem érdekelnek a tesztek
A VIPER hosszú távú projekteknél fog ragyogni, olyan alkalmazásoknál, ahol a biztonság és az üzleti logika magas, és amikor tesztelést kell végrehajtani.
Észre fogja venni, hogy a kódja tisztább lesz. Könnyen szét tudja majd választani a funkciók & moduljait a csapattagok között. Ha van egy UI formázási hiba, azonnal tudni fogja, hogy ellenőrizze a Presenter osztályt. Ha logikai hiba van, tudod, hogy az az interaktorból ered. Több egyértelműség, kevesebb méreg.
ADDITIONAL INFO
Ez a téma a try! Swift NYC 2017. Az előadás itt található.
Minden további try! Swift előadások itt találhatók.