I mitt tidligere innlegg i denne serien skrev jeg om Model-View-Controller-mønsteret og noen av dens feil. Til tross for de klare fordelene som MVC gir til utvikling av programvare, har den en tendens til å bli kort i store eller komplekse kakao applikasjoner.
Dette er ikke nyheter, skjønt. Flere arkitektoniske mønstre har dukket opp gjennom årene, med sikte på å løse manglene i modell-View-Controller-mønsteret. Du har kanskje hørt om MVP, Modell-View-Presenter, og MVVM, Model-View-ViewModel, for eksempel. Disse mønstrene ser ut og føles som modell-View-Controller-mønsteret, men de takler også noen av problemene som Model-View-Controller-mønsteret lider av.
Jeg hadde brukt Model-View-Controller mønsteret i mange år før jeg ved et uhell snublet over Model-View-ViewModel mønster. Det er ikke overraskende at MVVM er en latecomer til kakao-samfunnet, siden dets opprinnelse fører tilbake til Microsoft. Imidlertid har MVVM-mønsteret blitt sendt til kakao og tilpasset kravene og behovene til kakao-rammene og har nylig fått traksjon i kakao-samfunnet.
Mest appellerende er hvordan MVVM føles som en forbedret versjon av modell-View-Controller-mønsteret. Dette betyr at det ikke krever en dramatisk endring av tankegangen. Faktisk, når du forstår grunnleggende av mønsteret, er det ganske enkelt å implementere, ikke vanskeligere enn å implementere modell-View-Controller-mønsteret.
I forrige innlegg skrev jeg at kontrollerne i en typisk kakao-applikasjon er litt annerledes enn kontrollerne Reenskaug definert i det originale MVC-mønsteret. På IOS, for eksempel, kontrollerer en visningskontroller en visning. Det eneste ansvaret er å fylle utsikten det styrer og svare på brukerens samhandling. Men det er ikke det eneste ansvaret for å se kontrollører i de fleste iOS-applikasjoner, er det?
MVVM-mønsteret introduserer en fjerde komponent til blandingen, den se modell, som bidrar til å refokusere visningskontrolleren. Det gjør dette ved å ta over noen av ansvaret til visningskontrollen. Ta en titt på diagrammet nedenfor for å bedre forstå hvordan visningsmodellen passer inn i modell-View-ViewModel-mønsteret.
Som diagrammet illustrerer, har visningskontrollen ikke lenger modellen. Det er visningsmodellen som eier modellen, og visningskontrolleren ber om visningsmodellen for dataene den trenger å vise.
Dette er en viktig forskjell fra modell-View-Controller-mønsteret. Utsiktskontrollen har ingen direkte tilgang til modellen. Visningsmodellen gir visningskontrollen de dataene den trenger å vise i sin visning.
Forholdet mellom visningsregulatoren og dens visning forblir uendret. Det er viktig fordi det betyr at visningskontrollen kan fokusere utelukkende på å fylle sin visning og håndtere brukerinteraksjon. Det var det som visningskontrolleren var designet for.
Resultatet er ganske dramatisk. Utsikten kontrolleren er satt på en diett, og mange ansvar blir flyttet til visningsmodellen. Du slutter ikke lenger med en visningskontroller som spenner over hundrevis eller til og med tusenvis av kodelinjer.
Du lurer sikkert på hvordan visningsmodellen passer inn i det større bildet. Hva er oppgavene til visningsmodellen? Hvordan relaterer det seg til visningskontrolleren? Og hva med modellen?
Diagrammet jeg viste deg tidligere, gir oss et par tips. La oss starte med modellen. Modellen er ikke lenger eid av visningskontrolleren. Visningsmodellen eier modellen, og den fungerer som en proxy til visningskontrolleren. Når visningskontrolleren trenger et stykke data fra visningsmodellen, spør den sistnevnte modellen for de rå dataene og formaterer den på en slik måte at visningsstyreren umiddelbart kan bruke den i sin visning. Visningsregulatoren er ikke ansvarlig for datahåndtering og formatering.
Diagrammet viser også at modellen eies av visningsmodellen, ikke visningsregulatoren. Det er også verdt å påpeke at modell-View-ViewModel-mønsteret respekterer det nærtliggende forholdet mellom visningskontrolleren og dens visning, som er karakteristisk for kakao-applikasjoner. Det er derfor MVVM føles som en naturlig passform for kakao applikasjoner.
Fordi Model-View-ViewModel-mønsteret ikke er naturlig for kakao, er det ingen strenge regler for å implementere mønsteret. Dessverre er dette noe mange utviklere blir forvirret av. For å klargjøre noen få ting, vil jeg gjerne vise deg et grunnleggende eksempel på et program som bruker MVVM-mønsteret. Vi lager et veldig enkelt program som henter værdata for en forhåndsdefinert plassering fra Dark Sky API og viser gjeldende temperatur til brukeren.
Brann opp Xcode og opprett et nytt prosjekt basert på Enkeltvisningsprogram mal. Jeg bruker Xcode 8 og Swift 3 for denne opplæringen.
Gi navnet navnet på prosjektet MVVM, og sett Språk til Fort og enheter til iPhone.
I en typisk Cocoa-applikasjon drevet av modell-View-Controller-mønsteret, ville visningskontrolleren være ansvarlig for å utføre nettverksforespørselen. Du kan bruke en leder til å utføre nettverksforespørselen, men visningskontrolleren vil fortsatt vite om opprinnelsen til værdataene. Enda viktigere, det ville motta de rå dataene og måtte formatere den før den ble vist til brukeren. Dette er ikke den tilnærmingen vi tar når vi vedtar modell-View-ViewModel-mønsteret.
La oss lage en visningsmodell. Opprett en ny Swift-fil, gi den navnet WeatherViewViewModel.swift, og definer en klasse som heter WeatherViewViewModel
.
importere grunnlagsklasse WeatherViewViewModel
Ideen er enkel. Visningsregulatoren spør visningsmodellen for gjeldende temperatur for en forhåndsdefinert plassering. Fordi visningsmodellen sender en nettverksforespørsel til Dark Sky API, aksepterer metoden en lukning, som påberopes når visningsmodellen har data for visningsregulatoren. Disse dataene kan være den nåværende temperaturen, men det kan også være en feilmelding. Dette er hva currentTemperature (komplettering :)
Metoden for visningsmodellen ser ut som. Vi fyller ut detaljene om noen få øyeblikk.
importere Foundation Class WeatherViewViewModel // MARK: - Type Alias typealias CurrentTemperatureCompletion = (String) -> Gyldig // MARK: - Offentlig API func currentTemperature (ferdigstillelse: @konkurranse CurrentTemperatureCompletion)
Vi erklærer et type alias for enkelhets skyld og definerer en metode, currentTemperature (komplettering :)
, som aksepterer en lukning av type CurrentTemperatureCompletion
.
Implementeringen er ikke vanskelig hvis du er kjent med nettverk og URLSession
API. Ta en titt på koden under og legg merke til at jeg har brukt en enum, API
, å holde alt fint og ryddig.
importere Foundation class WeatherViewViewModel // MARK: - Type Alias typealias CurrentTemperatureCompletion = (String) -> Gyldig // MARK: - API enum API static let lat = 37.8267 statisk la lang = -122.4233 statisk la APIKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" statisk utelat baseURL = URL (streng: "https://api.darksky.net/varsel")! statisk var requestURL: URL return API.baseURL.appendingPathComponent (API.APIKey) .appendingPathComponent ("\ (lat), \ (lang)") // MARK: - Offentlig API func currentTemperature (ferdigstillelse: @kjøp CurrentTemperatureCompletion) let dataTask = URLSession.shared.dataTask (med: API.requestURL) [svakt selv] (data, svar, feil) i // Hjelpere var formatert Temperatur: String? hvis la data = data formattedTemperature = self?. temperatur (fra: data) DispatchQueue.main.async fullføring (formattedTemperature ?? "Kan ikke hente værdata") // Fortsett dataoppgave dataTask.resume ()
Det eneste koden jeg ikke har vist deg ennå, er implementeringen av temperatur (fra :)
metode. I denne metoden trekker vi ut den nåværende temperaturen fra Dark Sky-responsen.
// MARK: - Hjelpermetoder func temperatur (fra data: Data) -> String? vakt la JSON = prøv? JSONSerialization.jsonObject (med: data, alternativer: []) som? [String: Any] else return nil vakt la øyeblikket = JSON? ["For tiden"] som? [String: Enhver] ellers return nil vakt la temperatur = øyeblikket ["temperatur"] som? Dobbel annet return nil Return String (format: "% .0f ° F", temperatur)
I et produksjonsprogram vil jeg velge en mer robust løsning for å analysere svaret, for eksempel ObjectMapper eller Unbox.
Vi kan nå bruke visningsmodellen i visningsregulatoren. Vi lager en eiendom for visningsmodellen, og vi definerer også tre uttak for brukergrensesnittet.
importere UIKit klasse ViewController: UIViewController // MARK: - Egenskaper @IBOutlet var temperatureLabel: UILabel! // MARK: - @IBOutlet var henteWeatherDataButton: UIButton! // MARK: - @IBOutlet var aktivitetIndicatorView: UIActivityIndicatorView! // MARK: - privat la viewModel = WeatherViewViewModel ()
Legg merke til at visningskontrolleren eier visningsmodellen. I dette eksemplet er visningskontrolleren også ansvarlig for å instansere visningsmodellen. Generelt foretrekker jeg å injisere visningsmodellen i visningskontrolleren, men la oss holde det enkelt for nå.
I visningsregulatorens viewDidLoad ()
metode, anvender vi en hjelpemetode, fetchWeatherData ()
.
// MARK: - Se Life Cycle override func viewDidLoad () super.viewDidLoad () // Hent værdata fetchWeatherData ()
I fetchWeatherData ()
, Vi spør visningsmodellen for gjeldende temperatur. Før vi ber om temperaturen, skjuler vi etiketten og knappen og viser aktivitetsindikatorvisningen. I lukke passerer vi til fetchWeatherData (komplettering :)
, Vi oppdaterer brukergrensesnittet ved å fylle temperaturmerket og skjule aktivitetsindikatorvisningen.
// MARK: - Hjelpermetoder privat func fetchWeatherData () // Skjul brukergrensesnittstemperaturLabel.isHidden = true fetchWeatherDataButton.isHidden = true // Vis aktivitetsindikator Vis aktivitetIndicatorView.startAnimating () // Hent vær Data viewModel.currentTemperature [unowned selv] (temperatur) i // Oppdater Temperaturetikett self.temperatureLabel.text = Temperatur self.temperatureLabel.isHidden = false // Vis Hent Weather Data Button self.fetchWeatherDataButton.isHidden = false // Skjul Aktivitetsindikator Se self.activityIndicatorView.stopAnimating ()
Knappen er koblet til en handling, fetchWeatherData (_ :)
, der vi også påberoper fetchWeatherData ()
hjelpemetode. Som du kan se hjelper hjelperen oss med å unngå kod duplisering.
// MARK: - Handlinger @IBAction func fetchWeatherData (_ sender: Enhver) // Hent vær data fetchWeatherData ()
Det siste stykket av puslespillet er å skape brukergrensesnittet til eksempelapplikasjonen. Åpen Main.storyboard og legg til en etikett og en knapp til en vertikal stakkvisning. Vi legger også til en aktivitetsindikatorvisning øverst på stablingsvisningen, sentrert vertikalt og horisontalt.
Ikke glem å ta opp utsalgene og handlingen vi definerte i ViewController
klasse!
Nå bygger og kjører programmet for å prøve. Husk at du trenger en Dark Sky API-nøkkel for å få programmet til å fungere. Du kan registrere deg for en gratis konto på Dark Sky-nettsiden.
Selv om vi bare flyttet noen biter og stykker til visningsmodellen, kan du lure på hvorfor dette er nødvendig. Hva fikk vi? Hvorfor vil du legge til dette ekstra kompleksitetslaget?
Den mest åpenbare gevinst er at visningsstyreren er slankere og mer fokusert på å styre sin visning. Det er kjerneoppgaven til en visningscontroller: administrere visningen.
Men det er en mer subtil fordel. Fordi visningskontrolleren ikke er ansvarlig for å hente værdata fra Dark Sky API, er den ikke klar over detaljene knyttet til denne oppgaven. Værdataene kan komme fra en annen værservice eller fra et cachert svar. Visningskontrolleren ville ikke vite, og det trenger ikke å vite.
Testing forbedrer også dramatisk. Vis kontroller er kjent for å være vanskelig å teste på grunn av deres nært forhold til visningslaget. Ved å flytte noen av forretningslogikken til visningsmodellen, forbedrer vi umiddelbart testbarheten av prosjektet. Testvisningsmodeller er overraskende enkle fordi de ikke har en lenke til programmets visningslag.
Model-View-ViewModel-mønsteret er et viktig skritt fremover i utformingen av kakao-applikasjoner. Se kontroller er ikke så massive, se modeller er enklere å komponere og teste, og prosjektet blir mer håndterbart som følge av dette.
I denne korte serien kløpte vi bare overflaten. Det er mye mer å skrive om Model-View-ViewModel-mønsteret. Det har blitt et av mine favorittmønstre gjennom årene, og derfor fortsetter jeg å snakke og skrive om det. Gi det et forsøk og gi meg beskjed om hva du synes!
I mellomtiden, sjekk ut noen av våre andre innlegg om Swift og iOS app utvikling.