Ciao a tutti cari amici di iProg nell’articolo di oggi vorrei introdurvi una variante del pattern MVC ovvero MVP (Model-View-Presenter).
MVP permette di rendere le View indipendenti dalla gestione e creazione della logica di bussiness dividendo la logica dell’ applicazione in 3 livelli distinti, livelli che possono essere testati separatamente. La possibilità di poter testare i livelli separatamente è una delle caratteristiche del MVP.
Ma andiamo a vedere piu’ nel dettaglio questi layer:
Presenter
Il Presenter è il mediatore tra la view e il model, si occupa di recuperare i dati dal Model e li invia alle Views ma a differenza dell’ MVC, decide anche cosa succede quando si interagisce con la view.
View
La View conterrà un riferimento del Presenter in genere inviato attraverso dependency injector oppure utilizzando altri medoti che permettono di creare un riferimento al Presenter.
L’ unica cosa che la View dovrà fare è richiamare un metodo del Presenter ogni volta che ci sarà un’ interazione tra l’ interfaccia ( per esempio un click su un Button) e l’ accesso ai dati (db, request etc).
Model
Il Model rappresenta il punto di accesso ai dati. Trattasi di una o più classi che leggono dati dal DB, oppure da un servizio Web di qualsivoglia natura.
Dopo questa parte introduttiva mi sembra doveroso passare alla pratica, dopo aver creato un nuovo progetto in Xcode la prima cosa che andremo a fare e creare il nostro Model:
[swift]
struct Todo {
private var todos = [String]()
var count: Int { return todos.count }
mutating func insert(_ todo: String) {
todos.insert(todo, at: 0)
}
mutating func remove(at index: Int) {
guard todos.indices.contains(index) else { return }
todos.remove(at: index)
}
subscript(index: Int) -> String? {
return todos.indices.contains(index) ? todos[index] : nil
}
}
[/swift]
Dopo aver creato il model passsiamo all acreazione del Presenter:
[swift]
import Foundation
class TodoPresenter {
private var todo = Todo()
var count: Int { return todo.count }
func addTodo(newTodo:String) { todo.insert(newTodo) }
func removeTodo(at index:Int) { todo.remove(at: index) }
subscript(index:Int) -> String? {
guard let todoDescription = todo[index] else { return nil }
return todoDescription
}
}
[/swift]
Come possiamo notare il presenter non fa altro che interagire con il model (effettuando operazioni sui dati)
Infine andiamo a creare la nostra View
[swift]
import UIKit
class TodoViewController: UIViewController {
let presenter = TodoPresenter()
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
initialTodos()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == TodoVC.segueID {
if let indexPath = tableView.indexPathForSelectedRow {
if let todoDescription = presenter[indexPath.row] {
guard let todoDetailVC = segue.destination as? TodoDetailViewController else { return }
todoDetailVC.todoDescription = todoDescription
}
}
}
}
}
extension TodoViewController:UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return presenter.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: TodoVC.todoTVCellID, for: indexPath)
if let deailDescription = presenter[indexPath.row] {
cell.textLabel?.text = deailDescription
}
return cell
}
}
//MARK:- Helper Function
extension TodoViewController {
fileprivate func initialTodos() {
presenter.addTodo(newTodo: "Clean the house")
presenter.addTodo(newTodo: "Go to work")
presenter.addTodo(newTodo: "Go to sleep")
}
}
[/swift]
Come detto precedentemente la view utilizza il presenter per poter interaggire con i dati.
Questo era un’esempio molto banale e semplificato sull’utilizzo del MVP ma e’ possibile estenderlo a progetti e a strutture molto piu’ complesse.
Il progetto completo e’ scaricabile dal mio github
Separare la UI dalla logica non è sempre immediato ma il pattern Model-View-Presenter rende questo compito più facile evitando di creare classi che si occupano sia dei dati che delle interfacce, classi che spesso superano il migliaio di righe di codice.
Nelle applicazioni più grandi e complesse è obbligatorio organizzare bene il codice, altrimenti diventa impossibile estenderle e mantenerle nel tempo.