VIPER on monimutkainen, pelottava ja hyvin erilainen. VIPERin pinnan alla on ripaus myrkkyä ja se voi satuttaa. Ajan myötä kipu kuitenkin laantuu, ja siitä tulee yksi parannuskeino koodin jäsentämiseen. Hei hei myrkky. Hei onnellisuus.
Näihin iOS-kehittäjät voivat samaistua:
VIPER-suunnittelumalli aiheuttaa hämmennystä & väärinkäsityksiä monille kehittäjille. Tämän mallin edut & haitat ja käytännön käyttö ovat epäselviä. Työskenneltyäni MVC-, MVVM-, MVVM + RxSwift -kuvioilla toteutettujen sovellusten parissa halusin saada syvällisen ymmärryksen tästä ilkeältä kuulostavasta suunnittelukuviosta ja nähdä, mistä kohussa (hyssykässä?) on kyse. Tämän seurauksena päätin testata seuraavaa hypoteesia:
Kehittäjänä haluaisin käyttää VIPER-suunnittelumallia uudelleenkäytettävien moduulien rakentamiseen.
Kirjoitimme tämän postauksen toivoen, että se antaa sinulle mahdollisuuden arvioida, onko VIPER oikea vaihtoehto projektillesi tai kehyksellesi.
VIPER
Mutual Mobile -yhtiössä muutamat ihmiset halusivat parantaa testauksen kattavuutta iOS-sovelluksissaan. Tämän seurauksena he keksivät itse kuvion. Lisätietoa siitä, mistä se tuli, löytyy täältä.
Kirjaimellisesti Viper on lyhenne sanoista View, Interactor, Presenter, Entity, Router.
Teoreettisesti ottaen Viperiä käytettäessä sovelluksesi tai kehyksesi pilkotaan VIPER-”moduuleihin” eli uudelleenkäytettäviin & moduulikomponentteihin. Kaavion muodossa se kääntyy näin:
View: Käyttöliittymäkerros asettaa näkymän, päivittää kuvat ja tarrat, välittää käyttäjän syötteet esittäjälle
Interactor: Liiketoimintalogiikka, tietojen manipulointi, mallien muuntaminen, vuorovaikutus API-palveluiden kanssa & tiedonhallintaluokat. Välittää tulosteen esittelijälle
Presenter: Muotoilee vuorovaikuttajalta saadut tiedot näkymää varten, vastaanottaa syötteen näkymästä ja välittää sen vuorovaikuttajalle
Entity: Yksinkertainen tieto, joka edustaa sitä, mitä VIPER-moduulin toimintaan tarvitaan
Router: Vastaa moduulin navigoinnista ja sovelluksen navigoinnista
VIPER koodissa ei toimi samassa järjestyksessä kuin luetut kirjaimet. Toisin sanoen… koodisi toiminnot kulkevat enemmänkin alla olevassa järjestyksessä:
Interactor → Router , Entity, & Externals → Presenter → View
VIPERistä tulee enemmänkin IREPV… Se on sana jollekulle, jossain.
EXPERIMENTTI
Validoimme hypoteesin VIPERin käyttämisestä uudelleenkäytettävässä moduulissa rakentamalla frameworkin. Kehys keskittyy videosisällön toistamiseen. Videon toistamiseen liittyy käyttöliittymäpäivityksiä, tietojen lataamista & tietojen synkronointia. Nämä vuorovaikutussuhteet osoittivat, että videon toistomoduuli olisi varteenotettava ehdokas VIPER-rakenteiselle moduulille.
KATSAUS
VIPER-moduulit ovat protokollasuuntautuneita. Jokainen toiminto, ominaisuus, sisääntulo ja ulostulo on toteutettu protokollan avulla.
Tuloksena käytimme ajatusta:
Sopimus: swift-tiedosto, jossa kuvataan kaikki VIPER-moduulissa käytetyt protokollat. Se on samanlainen kuin objective-C:n otsikkotiedosto. Alla oleva koodinpätkä:
//*** 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()}
Toivoimme myös, että tämän viitekehyksen kehittäjät saisivat käyttöönsä helpon tavan määrittää VIPER-moduuleja.
Tuloksena käytimme ideaa:
Builder: Yksinkertainen objekti, jota käytetään VIPER-moduulin konfigurointiin ja riippuvuuksien asettamiseen. Se instantioi presenterin ja interactorin. Lopulta se palauttaa näkymän viewcontrolleriin.
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))
}}}
Käytimme myös protokollaa, jolla asetimme kaikki moduulin riippuvuudet:
class VPKDependencyManager: VPKDependencyManagerProtocol {
static func videoView(with interactor:VPKVideoPlaybackInteractorProtocol &VPKVideoPlaybackInteractorInputProtocol, ja 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
}}}
TOIMINTAA: VIDEOPLAYBACKKIT
Tarkastellaan käyttäjätarinaa:
**PLAY A VIDEO **
AVFOUNDATIONin avulla tiedämme seuraavaa:
- Pitäisi käyttää AVPlayeriä
- AVPlayer antaa sinulle AVPlayerLayerin, jonka voit lisätä näkymään
- AVPlayer vie aikaa – erityisesti etävideo-url:ien kanssa
- Edävideodatan lataaminen vaatii aikaa
-
Kaksi ensisijaista katselutoimintoa
- Toistaa
- Tauottaa
-
Tulokset näistä toiminnoista:
- AVPlayer vastaanottaa videon url:n ladattavaksi dataa varten
- Kun AVPlayer on valmis, AVPlayer palauttaa AVPlayerLayerin
- AvPlayerLayer on lisättävä näkymään
- Taukotilanteessa AVPlayerin on pysäytettävä toisto
**IREPV (AKA VIPER) & AVKit, AVFoundation **
INPUTS AND OUTPUTS
Nyt tämä on avainasemassa kuvion ymmärtämisessä, aivojen rauhoittamisessa, välttäen putoamisen kaninkolojen monimutkaisuuteen.
Major Lesson 1: Ymmärrä moduulisi tulot ja lähdöt.
View
- Input: didTapPlay
- Output: Presenter didTapPlay
Presenter
- Input: didTapPlay vastaanottaa view
- Output: onPlayerLayerSuccess(playerLayer) – tulostaa tämän presenterille
Interactor
- Input: didTapPlay – kertoo ulkoiselle managerille
-
Output: onVideoPlayerLayerSuccess – välittää soittotason presenterille
protokolla 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)}
Kun näkymä vastaanottaa AVPlayerLayerin esittäjältä, se voi ladata käyttöliittymänsä uudelleen.
protocol VPKVideoViewProtocol: class {func reloadInterface(with playerLayer: AVPlayerLayer)}
INTERAKTORIN JA PRESENTERIN SUHTEET
VIPERin osien väliset suhteet poikkeavat muista iOS-malleista. Jokainen objekti pitää kiinni toisistaan (heikosti tietysti). Voit ajatella, että yksi on toisen delegaatti. Katso alla oleva esimerkki:
-
Presenterillä on heikko viittaus interactoriin. Presenter välittää AINOASTAAN interactorin syöttöprotokollasta. ”didTapPlay”-syötteen välittäminen.
protocol VPKVideoPlaybackPresenterProtocol: class {var interactor: VPKVideoPlaybackInteractorInputProtocol? { get set }//weak}
-
Presenter välittää syöttöviestin eteenpäin; Presenter on kuitenkin myös riippuvainen siitä, että vuorovaikuttajat tuntevat videokokonaisuuden tiedot.
public class VPKVideoPlaybackPresenterPresenterProtocol:VPKVideoPlaybackPresenterProtocol {func didTapVideoView() {guard let safeVideoUrl = interactor?.videoType.videoUrl else {return }interactor?.didTapVideo(videoURL: safeVideoUrl, at:self.indexPath)}}}
PROTOCOL CONFORMANCE
Esittelijän ja vuorovaikuttajan suhde on tietovirran tärkein osa.
Presenter huolehtii Interactorin Outputista
Interactor huolehtii Presenterin Inputista
See alla:
Presenter noudattaa interactorin tulostusprotokollaa.
//MARK: INTERACTOR --> Presenterextension VPKVideoPlaybackPresenter:VPKVideoPlaybackInteractorOutputProtocol { func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer) {videoView?.reloadInterface(with: playerLayer)
}}}
Vuorovaikuttaja käyttää presenterin weak-ominaisuutta välittääkseen tulostiedot takaisin.
public class VPKVideoPlaybackInteractor:VPKVideoPlaybackInteractorProtocol {
weak var presenter: VPKVideoPlaybackInteractorOutputProtocol?
func playbackManager(_: VPKVideoPlaybackManager, didPrepareplayerLayer: AVPlayerLayer) {presenter?.onVideoLoadSuccess(playerLayer)
}}}
Plussat
MINUKSET
SUOSITUS
Suosittelemme käyttämään VIPER-muotoa, jos:
- Tiimisi on avoin uuden kuvion oppimiselle
- Tiimilläsi on vähintään 1 viikko aikaa oppia, ymmärtää ja kokeilla sitä
- Testauslähtöisen lähestymistavan tukeminen on helpompaa kuvion irrotetun luonteen vuoksi
- Olet innostunut siitä 🙂
- Tiimin jäsenet ovat vastuussa eri moduuleista
Me emme suosittele VIPER-muodon käyttöä, jos:
- Tiimisi on reaktiivinen ja ominaisuudet muuttuvat lähes päivittäin
- Rakennat pientä sovellusta, joka on kokeilu ja jota käytetään ongelman testaamiseen
- Et välitä testeistä
VIPER tulee loistamaan pitkäkestoisissa projekteissa, sovelluksissa, joissa tietoturva ja liiketoimintalogiikka ovat korkealla tasolla, ja kun testausta on pakko toteuttaa.
Huomaat, että koodistasi tulee puhtaampaa. Pystyt erottelemaan ominaisuuksia & moduuleista tiimin jäsenten kesken helposti. Kun käyttöliittymän muotoilussa on virhe, tiedät heti tarkistaa Presenter-luokan. Kun on looginen vika, tiedät sen johtuvan vuorovaikuttajasta. Enemmän selkeyttä, vähemmän myrkkyä.
LISÄTIETOA
Tämä aihe esiteltiin try! Swift NYC 2017. Esitys löytyy täältä.
Kaikki ylimääräiset try! Swift-esitykset löytyvät täältä.