iOS
MVP-Architektur
Suche…
Einführung
MVP ist ein Architekturmuster, eine Ableitung des Modell-Ansicht-Controllers. Es wird durch drei verschiedene Komponenten dargestellt: Modell, Ansicht und Präsentator. Es wurde entwickelt, um automatisierte Unit-Tests zu ermöglichen und die Trennung von Anliegen in der Präsentationslogik zu verbessern.
In Beispielen finden Sie ein einfaches Projekt, das mit dem MVP-Muster erstellt wurde.
Bemerkungen
Komponenten:
- Model ist eine Schnittstelle, die für die Domänendaten verantwortlich ist (die in der GUI angezeigt oder anderweitig bearbeitet werden sollen).
- Ansicht ist für die Präsentationsschicht (GUI) verantwortlich
- Presenter ist der "Mittelmensch" zwischen Model und View. Es reagiert auf die Aktionen des Benutzers, die in der Ansicht ausgeführt werden, ruft Daten aus dem Modell ab und formatiert sie für die Anzeige in der Ansicht
Pflichten der Komponente:
Modell | Aussicht | Moderator |
---|---|---|
Kommuniziert mit der DB-Schicht | Rendert Daten | Führt Abfragen an das Modell aus |
Geeignete Ereignisse auslösen | Empfängt Ereignisse | Formatiert Daten von Model |
Sehr grundlegende Validierungslogik | Sendet formatierte Daten an die Ansicht | |
Komplexe Validierungslogik | ||
Unterschiede zwischen MVC und MVP :
- View in MVC ist eng mit dem Controller gekoppelt. Der View-Teil des MVP besteht aus UIViews und UIViewController
- MVP View ist so dumm wie möglich und enthält fast keine Logik (wie in MVVM). MVC View verfügt über einige Geschäftslogik und kann das Modell abfragen
- MVP View behandelt Benutzergesten und delegiert die Interaktion an den Presenter. In MVC verarbeitet der Controller Gesten und Befehle Modell
- MVP-Pattern unterstützt Unit-Tests stark, MVC hat nur eingeschränkte Unterstützung
- MVC Controller hat viele UIKit-Abhängigkeiten, MVP Presenter hat keine
Pros:
- MVP macht UIViewController zu einem Teil der View-Komponente, dumm, passiv und ... weniger massiv;]
- Der größte Teil der Geschäftslogik ist aufgrund der dummen Ansichten verkapselt. Dies ergibt eine hervorragende Testbarkeit. Zum Testen des Domänenteils können Mock-Objekte eingeführt werden.
- Getrennte Einheiten sind leichter im Kopf zu halten, die Verantwortlichkeiten sind klar aufgeteilt.
Cons
- Sie werden mehr Code schreiben.
- Barriere für unerfahrene Entwickler oder für diejenigen, die noch nicht mit dem Muster arbeiten.
Hundewechsel
import Foundation
enum Breed: String {
case bulldog = "Bulldog"
case doberman = "Doberman"
case labrador = "Labrador"
}
struct Dog {
let name: String
let breed: String
let age: Int
}
DoggyView.swift
import Foundation
protocol DoggyView: NSObjectProtocol {
func startLoading()
func finishLoading()
func setDoggies(_ doggies: [DoggyViewData])
func setEmpty()
}
DoggyService.swift
import Foundation
typealias Result = ([Dog]) -> Void
class DoggyService {
func deliverDoggies(_ result: @escaping Result) {
let firstDoggy = Dog(name: "Alfred", breed: Breed.labrador.rawValue, age: 1)
let secondDoggy = Dog(name: "Vinny", breed: Breed.doberman.rawValue, age: 5)
let thirdDoggy = Dog(name: "Lucky", breed: Breed.labrador.rawValue, age: 3)
let delay = DispatchTime.now() + Double(Int64(Double(NSEC_PER_SEC)*2)) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: delay) {
result([firstDoggy,
secondDoggy,
thirdDoggy])
}
}
}
DoggyPresenter.swift
import Foundation
class DoggyPresenter {
// MARK: - Private
fileprivate let dogService: DoggyService
weak fileprivate var dogView: DoggyView?
init(dogService: DoggyService){
self.dogService = dogService
}
func attachView(_ attach: Bool, view: DoggyView?) {
if attach {
dogView = nil
} else {
if let view = view { dogView = view }
}
}
func getDogs(){
self.dogView?.startLoading()
dogService.deliverDoggies { [weak self] doggies in
self?.dogView?.finishLoading()
if doggies.count == 0 {
self?.dogView?.setEmpty()
} else {
self?.dogView?.setDoggies(doggies.map {
return DoggyViewData(name: "\($0.name) \($0.breed)",
age: "\($0.age)")
})
}
}
}
}
struct DoggyViewData {
let name: String
let age: String
}
DoggyListViewController.swift
import UIKit
class DoggyListViewController: UIViewController, UITableViewDataSource {
@IBOutlet weak var emptyView: UIView?
@IBOutlet weak var tableView: UITableView?
@IBOutlet weak var spinner: UIActivityIndicatorView?
fileprivate let dogPresenter = DoggyPresenter(dogService: DoggyService())
fileprivate var dogsToDisplay = [DoggyViewData]()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.dataSource = self
spinner?.hidesWhenStopped = true
dogPresenter.attachView(true, view: self)
dogPresenter.getDogs()
}
// MARK: DataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dogsToDisplay.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Cell")
let userViewData = dogsToDisplay[indexPath.row]
cell.textLabel?.text = userViewData.name
cell.detailTextLabel?.text = userViewData.age
return cell
}
}
extension DoggyListViewController: DoggyView {
func startLoading() {
spinner?.startAnimating()
}
func finishLoading() {
spinner?.stopAnimating()
}
func setDoggies(_ doggies: [DoggyViewData]) {
dogsToDisplay = doggies
tableView?.isHidden = false
emptyView?.isHidden = true;
tableView?.reloadData()
}
func setEmpty() {
tableView?.isHidden = true
emptyView?.isHidden = false;
}
}
Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow