Sätt dina kontroller på en diet med MVVM

I mitt tidigare inlägg i denna serie skrev jag om Model-View-Controller-mönstret och några av dess ofullkomligheter. Trots de tydliga fördelarna som MVC ger till mjukvaruutveckling tenderar den att förkortas i stora eller komplexa kakao applikationer.

Detta är dock inte nyheter. Flera arkitektoniska mönster har uppstått genom åren, med målet att ta itu med bristerna i modell-View-Controller-mönstret. Du kanske har hört talas om MVP, Model-View-Presenter, och MVVM, Model-View-ViewModel, till exempel. Dessa mönster ser ut som i modell-View-Controller-mönstret, men de hanterar också några av de problem som modell-View-Controller-mönstret lider av.

1. Varför Model-View-ViewModel

Jag hade använt Model-View-Controller mönstret i flera år innan jag oavsiktligt snubblat på Model-View-Viewmodel mönster. Det är inte förvånande att MVVM är en latecomer till kakaosamfundet, eftersom dess ursprung leder tillbaka till Microsoft. MVVM-mönstret har emellertid tagits fram till kakao och anpassat till kraven och behoven hos kakaoramarna och har nyligen vunnit dragkraft i kakaosamhället.

Mest tilltalande är hur MVVM känns som en förbättrad version av modell-View-Controller mönstret. Det innebär att det inte kräver en dramatisk förändring av tankegången. När du förstår grunden för mönstret är det faktiskt ganska enkelt att implementera, inte svårare än att implementera modell-View-Controller-mönstret.

2. Att lägga kontroller på en diet

I det föregående inlägget skrev jag att kontrollerna i en typisk kakaoapplikation är lite annorlunda än kontrollerna Reenskaug definierade i det ursprungliga MVC-mönstret. IOS kontrollerar till exempel en vy av en vy. Det enda ansvaret är att fylla den uppfattning som den hanterar och svara på användarens interaktion. Men det är inte det enda ansvaret att se kontrollerare i de flesta iOS-applikationer, är det?

MVVM-mönstret introducerar en fjärde komponent i mixen, den visa modell, vilket hjälper till att fokusera på visningsregulatorn. Det gör detta genom att ta över några av ansvarsförvaltarens ansvar. Ta en titt på diagrammet nedan för att bättre förstå hur visningsmodellen passar in i modell-View-ViewModel-mönstret.

Som diagrammet illustrerar, har visningsstyrenheten inte längre modellen. Det är den visningsmodell som äger modellen och visningscontrollern frågar visningsmodellen för de data som den behöver visa.

Detta är en viktig skillnad från modell-View-Controller-mönstret. Utsiktskontrollen har ingen direkt åtkomst till modellen. Visningsmodellen ger visningsanordningen den information som den behöver visa i sin vy.

Förhållandet mellan bildkontrollen och dess vy förblir oförändrad. Det är viktigt eftersom det innebär att visningscontrollern kan fokusera uteslutande på att fylla sin syn och hantera användarinteraktion. Det var det som visningsstyrenheten var avsedd för.

Resultatet är ganska dramatiskt. Utsiktskontrollen läggs på en kost, och många ansvarsområden flyttas till visningsmodellen. Du slutar inte längre med en kontroller som överstiger hundratals eller till och med tusentals kodrader.

3. Visningsmodellens ansvarsområden

Du undrar nog hur visningsmodellen passar in i den större bilden. Vad är uppgifterna för visningsmodellen? Hur hänför sig det till visningskontrollen? Och vad sägs om modellen?

Diagrammet jag visade dig tidigare ger oss några tips. Låt oss börja med modellen. Modellen ägs inte längre av vykontrollen. Visningsmodellen äger modellen, och den fungerar som en proxy för visningskontrollen. När visningsstyrenheten behöver en bit data från sin visningsmodell frågar den senare modellen för rådata och formaterar den på så sätt att visningsstyraren direkt kan använda den i sin uppfattning. Utsiktskontrollen är inte ansvarig för datahantering och formatering.

Diagrammet visar också att modellen ägs av visningsmodellen, inte visningsregulatorn. Det är också värt att påpeka att modell-View-ViewModel-mönstret respekterar närhetskontrollen för visningsregulatorn och dess syn, vilken är karakteristisk för kakaoapplikationer. Det är därför MVVM känns som en naturlig passform för kakao applikationer.

4. Ett exempel

Eftersom modell-View-ViewModel-mönstret inte är infödt till kakao, finns det inga strikta regler för att genomföra mönstret. Tyvärr är detta något som många utvecklare blir förvirrade av. För att klargöra några saker, skulle jag vilja visa dig ett grundläggande exempel på en applikation som använder MVVM-mönstret. Vi skapar en mycket enkel applikation som hämtar väderinformation för en fördefinierad plats från Dark Sky API och visar nuvarande temperatur för användaren.

Steg 1: Ställ in projektet

Avfyra Xcode och skapa ett nytt projekt baserat på Enkel visningsprogram mall. Jag använder Xcode 8 och Swift 3 för denna handledning.

Namn på projektet MVVM, och ställa in Språk till Snabb och enheter till iPhone.

Steg 2: Skapa en visningsmodell

I en typisk Cocoa-applikation som drivs av modell-View-Controller-mönstret, skulle vystyrningen vara ansvarig för att utföra nätverksförfrågan. Du kan använda en chef för att utföra nätverksförfrågan, men visningskontrollen skulle fortfarande veta om väderinformationens ursprung. Ännu viktigare, det skulle få den råa data och skulle behöva formatera den innan den visas till användaren. Detta är inte den metod vi tar när vi antar modell-View-ViewModel-mönstret.

Låt oss skapa en visningsmodell. Skapa en ny Swift-fil, namnge den WeatherViewViewModel.swift, och definiera en klass som heter WeatherViewViewModel.

importera Foundation-klassen WeatherViewViewModel 

Tanken är enkel. Utsiktskontrollen frågar visningsmodellen för den aktuella temperaturen för en fördefinierad plats. Eftersom visningsmodellen skickar en nätverksförfrågan till Dark Sky API, accepterar metoden en stängning, som påkallas när visningsmodellen har data för visningsstyrningen. Dessa data kan vara den aktuella temperaturen, men det kan också vara ett felmeddelande. Det här är vad currentTemperature (slutförande :) Metoden för visningsmodellen ser ut. Vi fyller i detaljerna om några minuter.

importera Foundation class WeatherViewViewModel // MARK: - Typ Alias ​​typalias CurrentTemperatureCompletion = (String) -> Avbryt // MARK: - Offentligt API func currentTemperature (slutförandeskap: @escaping CurrentTemperatureCompletion) 

Vi förklarar ett typ alias för enkelhets skyld och definierar en metod, currentTemperature (slutförande :), som accepterar en tillslutning av typ CurrentTemperatureCompletion

Genomförandet är inte svårt om du är bekant med nätverk och URLSession API. Ta en titt på koden nedan och märka att jag har använt en enum, API, för att hålla allt fint och städat.

importera Foundation class WeatherViewViewModel // MARK: - Typ Alias ​​typalias CurrentTemperatureCompletion = (String) -> Radera // MARK: - API enum API static let lat = 37.8267 statisk låt lång = -122.4233 statisk låt APIKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" statisk låt baseURL = URL (sträng: "https://api.darksky.net/forecast")! static request requestURL: URL return API.baseURL.appendingPathComponent (API.APIKey) .appendingPathComponent ("\ (lat), \ (long)") // MARK: - Offentligt API func currentTemperature (slutförandeskap: @avslutande CurrentTemperatureCompletion) let dataTask = URLSession.shared.dataTask (med: API.requestURL) [svagt självt] (data, svar, fel) i // Hjälpare var formaterad Temperatur: String? om laddat data = data formattedTemperature = self? .temperature (from: data) DispatchQueue.main.async completion (formattedTemperature ?? "Kan inte hämta vädret data") // Fortsätt datauppgift dataTask.resume () 

Det enda kodstycket som jag inte har visat dig ännu är genomförandet av temperatur (från :) metod. I denna metod extraherar vi den nuvarande temperaturen från Dark Sky-svaret.

// MARK: - Hjälparmetoder func temperatur (från data: Data) -> String? guard let JSON = försök? JSONSerialization.jsonObject (med: data, alternativ: []) som? [String: Any] else return nil vakt låt nuvarande = JSON? ["För närvarande"] som? [String: Any] else return nil guard let temperatur = för närvarande ["temperatur"] som? Dubbel annan return nil returnera String (format: "% .0f ° F", temperatur)

I en produktionsapplikation skulle jag välja en mer robust lösning för att analysera svaret, till exempel ObjectMapper eller Unbox.

Steg 3: Integrera View-modellen

Vi kan nu använda visningsmodellen i vykontrollen. Vi skapar en egenskap för visningsmodellen, och vi definierar också tre uttag för användargränssnittet.

importera UIKit klass ViewController: UIViewController // MARK: - Egenskaper @IBOutlet var temperatureLabel: UILabel! // MARK: - @IBOutlet var fetchWeatherDataButton: UIButton! // MARK: - @IBOutlet var activityIndicatorView: UIActivityIndicatorView! // MARK: - privat låt viewModel = WeatherViewViewModel ()

Observera att visningsstyrenheten äger visningsmodellen. I det här exemplet är visningsstyrenheten också ansvarig för att instansera sin visningsmodell. I allmänhet föredrar jag att injicera visningsmodellen i vykontrollen, men låt oss hålla det enkelt för nu.

I visningsregulatorens viewDidLoad () metod, vi åberopar en hjälpmetod, fetchWeatherData ().

// MARK: - Visa livscykelöverstyrning func viewDidLoad () super.viewDidLoad () // Hämta väderdata fetchWeatherData ()

I fetchWeatherData (), vi frågar visningsmodellen för den aktuella temperaturen. Innan vi begär temperaturen döljer vi etiketten och knappen och visar aktivitetsindikatorvyn. I stängningen passerar vi till fetchWeatherData (slutförande :), Vi uppdaterar användargränssnittet genom att fylla i temperaturmärket och gömma aktivitetsindikatorn.

// MARK: - Hjälparmetoder privat func fetchWeatherData () // Dölj användargränssnittstemperaturLabel.isHidden = true fetchWeatherDataButton.isHidden = true // Visa Aktivitetsindikator Visa aktivitetIndicatorView.startAnimating () // Hämta Vädret Data viewModel.currentTemperature [unowned själv] (temperatur) i // Uppdaterad temperatur etikett self.temperatureLabel.text = temperatur self.temperatureLabel.isHidden = false // Visa Hämta Weather Data Button self.fetchWeatherDataButton.isHidden = false // Dölj Aktivitetsindikator Visa self.activityIndicatorView.stopAnimating ()

Knappen är ansluten till en åtgärd, fetchWeatherData (_ :), där vi också åberopa fetchWeatherData () hjälparmetod. Som du kan se hjälper hjälpmedlet oss att undvika koddubbling.

// MARK: - Åtgärder @IBAction func fetchWeatherData (_ avsändare: Alla) // Hämta Weather Data fetchWeatherData ()

Steg 4: Skapa användargränssnittet

Det sista stycket av pusslet är att skapa användargränssnittet i exemplet applikationen. Öppna Main.storyboard och lägg till en etikett och en knapp till en vertikal stapelvy. Vi lägger också till en aktivitetsindikatorvy ovanpå stapelvyn, centrerad vertikalt och horisontellt.

Glöm inte att ansluta uttagen och den åtgärd vi definierade i ViewController klass!

Nu bygg och kör programmet för att prova. Kom ihåg att du behöver en Dark Sky API-nyckel för att få programmet att fungera. Du kan registrera dig för ett gratis konto på Dark Sky hemsida.

5. Vilka är fördelarna?

Även om vi bara flyttat några bitar till visningsmodellen kanske du undrar varför det är nödvändigt. Vad fick vi? Varför skulle du lägga till detta extra lager av komplexitet?

Den uppenbara vinsten är att visningscontrollern är smalare och mer fokuserad på att hantera sin uppfattning. Det är kärnansvaret för en visningsansvarig: hanterar sin uppfattning.

Men det finns en mer subtil fördel. Eftersom visningskontrollen inte är ansvarig för att hämta väderinformationen från Dark Sky API, är den inte medveten om detaljerna i samband med denna uppgift. Vädret data kan komma från en annan väderstjänst eller från ett cachet svar. Utsiktskontrollen skulle inte veta, och det behöver inte veta.

Testning förbättras också dramatiskt. Visa kontroller är kända för att vara svåra att testa på grund av deras nära relation till visningsskiktet. Genom att flytta några av affärslogiken till visningsmodellen förbättrar vi omedelbart testbarheten i projektet. Testmodeller är överraskande lätta eftersom de inte har en länk till programmets visningslager.

Slutsats

Model-View-ViewModel-mönstret är ett viktigt framsteg när det gäller att utforma kakaoapplikationer. Visa kontroller är inte så stora, visa modeller är enklare att komponera och testa, och ditt projekt blir mer hanterbart som ett resultat.

I den här korta serien skrapade vi bara ytan. Det finns mycket mer att skriva om modell-View-ViewModel-mönstret. Det har blivit en av mina favoritmönster under åren, och därför fortsätter jag att prata och skriva om det. Ge det ett försök och låt mig veta vad du tycker!

Under tiden, kolla in några av våra andra inlägg om Swift och iOS app utveckling.