MVP — vad är det, mönstret Model-View-Presenter i iOS och Android

Författare: IT Sectr Publicerad: 2026-02-16 Lästid: 10 min

MVP (Model-View-Presenter) — arkitekturmönster där Presenter fungerar som medlare mellan Model och View via ViewContract-gränssnittet. Till skillnad från MVC, där Controller direkt hanterar View via UIKit, är Presenter inte beroende av ramverket — det arbetar genom abstraktion, vilket gör det testbart utan Android SDK eller UIKit. MVP användes i stor utsträckning inom Android-utveckling före Jetpack och förblir relevant för legacy-projekt. Mer i Martin Fowlers artikel.

Huvudpunkter

  • MVP — tre komponenter: Model (data), View (gränssnitt), Presenter (logik och tillstånd)
  • ViewContract — gränssnittet genom vilket Presenter kommunicerar med View, vilket säkerställer testbarhet
  • Presenter — innehåller all affärslogik, är inte beroende av plattformsklasser Android/iOS
  • Passive View — View är maximalt passiv, visar endast data på Presenters kommandon
  • MVP vs MVC — Presenter testas med enhetstester, Controller i MVC är beroende av UIKit/Android Framework

Vad är MVP: essensen av mönstret Model-View-Presenter

MVP (Model-View-Presenter) — arkitekturmönster föreslaget av Martin Fowler i början av 2000-talet som en evolution av MVC för att förbättra testbarheten av användargränssnittet. Model hanterar data och affärslogik, View ansvarar för visning och bearbetning av användarinmatning, Presenter — central komponent som tar emot händelser från View, hämtar data från Model och formar tillstånd för visning.

Den huvudsakliga skillnaden mellan MVP och MVC — Presenter har ingen direkt referens till View. Istället interagerar Presenter med View via ViewContract-gränssnittet. View implementerar detta gränssnitt och överför sig själv till Presenter. Detta bryter beroendet av UIKit (iOS) eller Android Framework — Presenter testas isolerat med en mock-implementering av ViewContract. I MVC uppdaterar controller UIViewController direkt UILabel, i MVP anropar Presenter metoden view.showName(name) och View bestämmer hur det ska visas.

KomponentAnsvarTestbarhet
ModelData, affärslogik, nätverksanropEnhetstester (oberoende av UI)
ViewVisning av UI, överföring av händelser till PresenterMock-implementering via gränssnitt
PresenterAffärslogik, tillståndshantering, navigeringEnhetstester (via ViewContract mock)

Principen om ensamt ansvar efterlevs striktare i MVP än i MVC: View ansvarar endast för rendering, Model — för data, Presenter — för logik och koordinering. I verkliga projekt upptar Presenter 40–60% av skärmkoden, View — 20–30%, Model — 20–30%. Denna fördelning gör det möjligt att testa viktig affärslogik utan att starta Android-emulatorn eller iOS-simulatorn.

MVP i Android: Presenter, ViewContract och Activity

MVP i Android använder Activity eller Fragment som View som implementerar ViewContract — ett gränssnitt med metoder för datavisning. Presenter skapas i Activity, kopplar View till sig själv och hanterar dataladdning. Vid skärmrotation återskapas Activity — Presenter kan bevaras via retain-fragment eller extern lagring, vilket löser problemet med tillståndsförlust som är typiskt för ren MVC.

kotlin
// ViewContract — gränssnitt för kommunikation mellan Presenter och View
interface UserView {
    fun showLoading()
    fun hideLoading()
    fun showUser(user: User)
    fun showError(message: String)
}

// Presenter — testbart logiklager
class UserPresenter(
    private val repository: UserRepository
) {
    private var view: UserView? = null

    fun attachView(view: UserView) {
        this.view = view
    }

    fun detachView() {
        view = null
    }

    fun loadUser(userId: Int) {
        view?.showLoading()
        repository.getUser(userId) { result ->
            view?.hideLoading()
            result.onSuccess { user ->
                view?.showUser(user)
            }.onFailure { e ->
                view?.showError(e.message ?: "Okänt fel")
            }
        }
    }
}

// View (Activity) implementerar gränssnittet
class UserActivity : AppCompatActivity(), UserView {
    private val presenter = UserPresenter(UserRepository())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        presenter.attachView(this)
        presenter.loadUser(42)
    }

    override fun onDestroy() {
        presenter.detachView()
        super.onDestroy()
    }

    override fun showUser(user: User) { /* uppdatera UI */ }
    override fun showLoading() { /* visa ProgressBar */ }
    override fun hideLoading() { /* dölj ProgressBar */ }
    override fun showError(message: String) { /* visa Snackbar */ }
}

Livscykelhantering — det viktigaste problemet med MVP på Android. Activity förstörs vid skärmrotation och presenter.attachView() anropas igen i onCreate(). Om dataladdning är asynkron (RxJava, korutiner), kan View vid slutförandet vara frånkopplad. Lösning — avbryta prenumerationer i detachView() eller använda Loader från Support Library (för projekt utan Jetpack). På IT Sectr använde vi i åratal kombinationen MVP + RxJava i kommersiella projekt — mönstret är stabilt men kräver disciplin i prenumerationshantering.

Retain-fragment — mekanism för att bevara Presenter vid skärmrotation. Fragment utan UI (setRetainInstance(true)) lever längre än Activity och lagrar en referens till Presenter. Vid återskapande av Activity överför fragmentet samma Presenter till den nya Activity. Retain-fragment är föråldrade sedan AndroidX, men deras pre-Jetpack-motsvarighet (Fragment.setRetainInstance) fungerar fortfarande i legacy-projekt. I modern utveckling rekommenderar Google ViewModel istället för retain-fragment.

MVP i iOS: Presenter och View Protocol

MVP i iOS byggs via ett View-protokoll. UIViewController implementerar protokollet, Presenter importerar inte UIKit och testas rent. Till skillnad från Apple MVC, där UIViewController själv innehåller logik och direkta IBOutlet-anslutningar, hanterar Presenter tillståndet och ger kommandon till View via protokollmetoder. View fattar inga beslut — det utför Presenters kommandon: showUser, showLoading, navigateToProfile.

swift
import Foundation

// View Protocol — abstraktion för Presenter
protocol UserViewProtocol: AnyObject {
    func showLoading()
    func hideLoading()
    func display(user: User)
    func displayError(message: String)
}

// Presenter — ren logik, utan UIKit
final class UserPresenter {
    private weak var view: UserViewProtocol?
    private let service: UserServiceProtocol

    init(service: UserServiceProtocol) {
        self.service = service
    }

    func attach(view: UserViewProtocol) {
        self.view = view
    }

    func detach() {
        view = nil
    }

    func loadUser(id: Int) {
        view?.showLoading()
        service.fetchUser(id: id) { [weak self] result in
            guard let self else { return }
            self.view?.hideLoading()
            switch result {
            case .success(let user):
                self.view?.display(user: user)
            case .failure(let error):
                self.view?.displayError(message: error.localizedDescription)
            }
        }
    }
}

// View (UIViewController) implementerar protokollet
final class UserViewController: UIViewController, UserViewProtocol {
    private let presenter = UserPresenter(service: UserService())

    override func viewDidLoad() {
        super.viewDidLoad()
        presenter.attach(view: self)
        presenter.loadUser(id: 42)
    }

    func display(user: User) {
        nameLabel.text = user.name
    }
    // ... resten av protokollets metoder
}

Weak reference till View — ett obligatoriskt villkor i iOS MVP. UIViewController kan förstöras (pop från navigeringsstacken) och dess closure i Presenter skapar en retain cycle. En svag referens (weak var) garanterar att View frigörs när den lämnar skärmen, oavsett förekomsten av asynkrona operationer i Presenter. I Android löses ett liknande problem via detachView() — anrop i onDestroy() nollställer referensen till View.

Passive View vs Supervising Controller — två varianter av MVP från Martin Fowler. Passive View: View innehåller ingen logik, Presenter hanterar tillståndet fullständigt. Supervising Controller: View gör själv enkel databindning (t.ex. via data binding), Presenter ingriper endast i komplexa scenarier. Inom mobil utveckling används Passive View oftare — det ger maximal testbarhet och förutsägbarhet av skärmtillståndet.

Skillnader mellan MVP och MVC och fördelar med testning

Den huvudsakliga skillnaden mellan MVP och MVC — sättet att kommunicera med View. I MVC har Controller en direkt referens till View (UIViewController.IBOutlets, Activity.findViewById). I MVP interagerar Presenter med View via ViewContract-gränssnittet. Denna skillnad förändrar testbarheten radikalt: ett Mock-objekt som implementerar ViewContract gör det möjligt att kontrollera Presenters logik utan att starta applikationen, emulatorn och UI-ramverket.

KriteriumMVCMVP
Anslutning till ViewDirekt (Controller → View)Via gränssnitt (Presenter → ViewContract)
Testning av logikKräver UIKit/Android FrameworkEnhetstester utan plattformsberoenden
LivscykelController lever med skärmenPresenter kan leva längre (retain)
KomplexitetMinimal+1 gränssnitt per skärm
Massive ControllerTypiskt problemLogik i Presenter, View tunn

Exempel på enhetstest av Presenter i Kotlin: en mock UserView skapas, skickas till Presenter, loadUser anropas, kontrollera om showUser anropades med korrekt data. Testet körs på millisekunder, kräver ingen emulator. På iOS liknande — OCMock eller protokoll-stubb kontrollerar anrop av UserViewProtocol-metoder. I IT Sectrs projekt med MVP nådde täckningen av affärslogik med enhetstester 85–90%, 2–3 gånger högre än i liknande MVC-projekt.

När MVP är att föredra framför MVC — i projekt med stränga krav på stabilitet: bankapplikationer, medicinska system, betalterminaler. Inom dessa områden är kostnaden för fel hög och enhetstester är kritiska. I Post-MVP-projekt (när produkten redan är på marknaden men kodbasen är legacy) gör MVP det möjligt att gradvis flytta logik från Massive View Controller till en testbar nivå utan att helt skriva om arkitekturen.

Begränsningar av MVP och övergång till MVVM

De största nackdelarna med MVP — ökat antal gränssnitt och manuell prenumerationshantering. Varje skärm kräver minst ett ViewContract + Presenter, för 50 skärmar — 50 gränssnitt och 50 Presenter-klasser. I MVVM ersätter ViewModel Presenter och använder reaktiva mekanismer (LiveData, StateFlow, ObservableObject), vilket eliminerar behovet av manuell attach/detach och ViewContract-gränssnitt.

RxJava och MVP — populär kombination i Android 2015–2019. Presenter prenumererar på Observable från Repository, visar resultat via ViewContract. Problem: disposable måste explicit avbrytas i detachView(), annars orsakar en prenumerationsläcka en krasch vid uppdatering av frånkopplad View. Biblioteken RxLifecycle och AutoDispose automatiserade delvis avbrutet, men lade till beroende. På IT Sectr bytte vi från MVP+RxJava till MVVM+Flow 2020 — koden blev 25–30% kortare tack vare att ViewContract försvann.

Migrering från MVP till MVVM — en stegvis process. 1) Ersätt ViewContract med LiveData/StateFlow i Presenter. 2) Ta bort metoder för attach/detach — prenumeration sker via observe(). 3) Byt namn på Presenter till ViewModel. 4) Integrera DI (Hilt/Koin) för ViewModelFactory. Migrering av en skärm tar 2–4 timmar, av hela kodbasen — 2–4 veckor för ett projekt med 50–100 skärmar. Efter migrering tas ViewContract-gränssnitten bort, koden förkortas, testerna finns kvar.

MVP i modern utveckling — mönstret lever, men förlorar mot MVVM och MVI. Google rekommenderar officiellt MVVM med Jetpack för nya projekt. Apple — MVVM med SwiftUI. Kunskap om MVP är dock obligatorisk för arbete med legacy-kod: hundratals Android-applikationer i Google Play kör fortfarande på MVP, inklusive applikationer från stora banker, detaljhandlare och transportföretag. Förståelse av MVP är grunden för att bemästra MVI och Clean Architecture, eftersom Presenter är den direkta föregångaren till Use Case i Robert Martins terminologi.

Vanliga frågor

Vad skiljer MVP från MVC?

I MVP interagerar Presenter med View via ViewContract-gränssnittet, inte direkt. I MVC har Controller en direkt referens till View via IBOutlet/findViewById. MVP gör det möjligt att testa Presenter med enhetstester utan iOS Simulator eller Android Emulator, eftersom Presenter inte är beroende av UIKit eller Android Framework. MVC kräver att applikationen startas för att testa controllern.

När ska jag använda MVP istället för MVVM?

MVP är motiverat i legacy-projekt som redan är byggda på detta mönster och i applikationer utan stöd för reaktiva mekanismer (LiveData, StateFlow, Combine). För nya projekt rekommenderar Google MVVM med Jetpack (Android) och Apple rekommenderar MVVM med SwiftUI (iOS). MVP förblir det bästa valet för projekt på ren UIKit utan Combine när enhetstestning av affärslogik krävs.

Hur löser man problemet med att Presenter försvinner vid skärmrotation?

På Android — använd retain-fragment (setRetainInstance(true)) eller ViewModel från Jetpack. Retain-fragment bevarar Presenter vid rotation och överför det till den nya Activity. ViewModel från Google — ett modernt alternativ som automatiskt bevarar tillståndet vid rotation utan retain-fragment. På iOS — Presenter skapas på nytt vid varje viewDidLoad, men cachas i en separat koordinatortjänst.

Hur många klasser behövs för en skärm i MVP?

Minimum 4: ViewContract-gränssnitt, ViewContract-implementering (Activity/Fragment), Presenter, Model (Repository). Om Dagger/Hilt används, läggs en DI-modul till. För 50 skärmar är det 200+ klasser. MVVM minskar antalet med 1 fil per skärm (ViewContract behövs inte), MVI lägger till State- och Intent-klasser. Antalet klasser — det främsta argumentet mot MVP i stora projekt.

Vad är skillnaden mellan Passive View och Supervising Controller i MVP?

Passive View — View innehåller ingen logik, Presenter hanterar fullständigt tillstånd och data. Supervising Controller — View gör själv enkel bindning (data binding), Presenter ingriper i komplexa scenarier. Inom mobil utveckling dominerar Passive View — ger maximal testbarhet och förutsägbarhet. Supervising Controller används i webbramverk (ASP.NET Web Forms, GWT).

Sammanfattning

  • MVP (Model-View-Presenter) — evolution av MVC med en testbar Presenter-nivå via ViewContract-gränssnittet
  • ViewContract — gränssnitt som abstraherar View från Presenter, möjliggör mock-testning
  • Passive View — dominerande MVP-variant inom mobil utveckling med passiv View
  • Presenter — innehåller affärslogik, är inte beroende av UIKit eller Android Framework
  • MVP vs MVC — MVP löser testningsproblemet men lägger till 1 gränssnitt per skärm
  • Android retain-fragment — bevarande av Presenter vid skärmrotation före Jetpack ViewModel
  • Migrering till MVVM — ersättning av ViewContract med LiveData/StateFlow förkortar kod med 25–30%

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också