MVP in Swift

      Nessun commento su MVP in Swift

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:

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
    }
}

Dopo aver creato il model passsiamo all acreazione del Presenter:

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
    }
}

Come possiamo notare il presenter non fa altro che interagire con il model (effettuando operazioni sui dati)

Infine andiamo a creare la nostra View

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")
    }
}

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.