Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Progressive MVC Architecture

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Progressive MVC Architecture

Avatar for Swift India

Swift India

November 24, 2018
Tweet

More Decks by Swift India

Other Decks in Programming

Transcript

  1. About Me • iOS Engineer at PhonePe • Creator of

    AarKay.xyz • OpenSource Work • Restofire • SwiftFrameworkTemplate
  2. • SQLite • Core Data • Realm • Network (URLSession)

    • Files • User Defaults • iCloud • Firebase • …..
  3. import UIKit import CoreData class PersonListViewController: UIViewController { var coreDataStack:

    CoreDataStack! var people: [CDPerson] = [] override func viewDidLoad() { super.viewDidLoad() let managedContext = coreDataStack.persistentContainer.viewContext let fetchRequest = NSFetchRequest<CDPerson>(entityName: "Person") do { people = try managedContext.fetch(fetchRequest) } catch let error as NSError { print("Could not fetch. \(error), \(error.userInfo)") } } }
  4. import UIKit import CoreData class AddPersonViewController: UIViewController { var coreDataStack:

    CoreDataStack! @IBAction func addPerson(_ sender: Any) { let name = nameTextField.text!; let age = Int16(ageTextField.text!)!; let managedContext = coreDataStack.persistentContainer.viewContext let person = CDPerson(context: managedContext) person.name = name person.age = age do { try managedContext.save() delegate?.didAddPerson(person) navigationController?.popViewController(animated: true) } catch let error as NSError { print("Could not save. \(error), \(error.userInfo)") } } }
  5. Problems • Tightly Coupled with ViewControllers • Hard to substitute

    with TestData or URL • Hard to test the models
  6. import Foundation import CoreData struct Person: Codable { let name:

    String let age: Int16 init(name: String, age: Int16) { self.name = name self.age = age } static func getAll() throws -> [Person] { let managedContext = CoreDataStack.shared.persistentContainer.viewContext let fetchRequest = NSFetchRequest<CDPerson>(entityName: "Person") do { let people = try managedContext.fetch(fetchRequest) return people.map { Person(name: $0.name, age: $0.age) } } catch { fatalError() } } }
  7. class CDPersonProvider { let coreDataStack: CoreDataStack init(coreDataStack: CoreDataStack) { self.coreDataStack

    = coreDataStack } func getAll() throws -> [Person] { let managedContext = coreDataStack.persistentContainer.viewContext let fetchRequest = NSFetchRequest<CDPerson>(entityName: "Person") do { let people = try managedContext.fetch(fetchRequest) return people.map { Person(name: $0.name, age: $0.age) } } catch { throw PersonError.fetchError } } }
  8. import UIKit class PersonListViewController: UIViewController { var personService: CDPersonProvider! @IBOutlet

    weak var tableview: PersonListView! override func viewDidLoad() { super.viewDidLoad() title = "People" do { let people = try personService.getAll() tableview.people = people tableview.reloadData() } catch { // Handle error } } }
  9. import UIKit class PersonListViewController: UIViewController { var personService: PersonService! @IBOutlet

    weak var tableview: PersonListView! override func viewDidLoad() { super.viewDidLoad() title = "People" do { let people = try personService.getAll() tableview.people = people tableview.reloadData() } catch { // Handle error } } }
  10. class CDPersonProvider: PersonService { let coreDataStack: CoreDataStack init(coreDataStack: CoreDataStack) {

    self.coreDataStack = coreDataStack } func getAll() throws -> [Person] { let managedContext = coreDataStack.persistentContainer.viewContext let fetchRequest = NSFetchRequest<CDPerson>(entityName: "Person") do { let people = try managedContext.fetch(fetchRequest) return people.map { Person(name: $0.name, age: $0.age) } } catch { throw PersonError.fetchError } } }
  11. import UIKit class PersonListViewController: UIViewController { @IBOutlet weak var tableview:

    UITableView! override func viewDidLoad() { super.viewDidLoad() tableview.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") } func tableView( _ tableView: UITableView, numberOfRowsInSection section: Int ) -> Int { return people.count } func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { let person = people[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) let text = person.name + " - " + String(person.age) cell.textLabel?.text = text return cell } }
  12. import UIKit class AddPersonViewController: UIViewController { @IBOutlet weak var nameTextField:

    UITextField! @IBOutlet weak var ageTextField: UITextField! @IBOutlet weak var addButton: UIButton! @IBAction func addPerson(_ sender: Any) { let name = nameTextField.text!; let age = Int16(ageTextField.text!)!; let person = Person(name: name, age: age) do { try personService.createPerson(person) delegate?.didAddPerson(person) navigationController?.popViewController(animated: true) } catch { // Handle Error } } }
  13. import UIKit protocol AddPersonViewDelegate: class { func didAddPerson(_ person: Person)

    } class AddPersonView: UIView { weak var addPersonViewDelegate: AddPersonViewDelegate? @IBOutlet weak var nameTextField: UITextField! @IBOutlet weak var ageTextField: UITextField! @IBOutlet weak var addButton: UIButton! @IBAction func addPerson(_ sender: Any) { let name = nameTextField.text! let age = Int16(ageTextField.text!)! let person = Person(name: name, age: age) delegate?.didAddPerson(person) } }
  14. import UIKit protocol AddPersonDelegate { func didAddPerson(_ person: Person) }

    class AddPersonViewController: UIViewController, AddPersonViewDelegate { var personService: PersonService! var delegate: AddPersonDelegate? @IBOutlet weak var addPersonView: AddPersonView! override func viewDidLoad() { super.viewDidLoad() title = "Add person" } func didAddPerson(_ person: Person) { do { try personService.createPerson(person) delegate?.didAddPerson(person) navigationController?.popViewController(animated: true) } catch { // Handle Error } } }
  15. ViewModels • Aka Presenters • Data Formatting from Model •

    Transforming User Inputs back to Model • Testable
  16. import Foundation struct AddPersonViewModel { var name: String? var age:

    String? init() {} func person() -> Person? { guard let name = name, let age = age, let numberedAge = Int16(age) else { return nil } return Person(name: name, age: numberedAge) } }
  17. import UIKit protocol AddPersonViewDelegate: class { func didAddPerson(_ person: Person)

    } class AddPersonView: UIView { weak var addPersonViewDelegate: AddPersonViewDelegate? private var addPersonViewModel = AddPersonViewModel() @IBOutlet weak var nameTextField: UITextField! @IBOutlet weak var ageTextField: UITextField! @IBOutlet weak var addButton: UIButton! @IBAction func addPerson(_ sender: Any) { addPersonViewModel.name = nameTextField.text addPersonViewModel.age = ageTextField.text if let person = addPersonViewModel.person() { addPersonViewDelegate?.didAddPerson(person) } else { // Handle validation error } } }
  18. import UIKit class AddPersonView: UIView, NameTextFieldDelegate, AgeTextFieldDelegate, AddButtonDelegate { @IBOutlet

    weak var nameTextField: NameTextField! @IBOutlet weak var ageTextField: AgeTextField! @IBOutlet weak var addButton: AddButton! override func awakeFromNib() { super.awakeFromNib() nameTextField.nameTextFieldDelegate = self ageTextField.ageTextFieldDelegate = self addButton.addButtonDelegate = self } }
  19. import UIKit class PersonListViewController: UIViewController { var personService: PersonService! override

    func viewDidLoad() { super.viewDidLoad() title = "People" do { let people = try personService.getAll() people.filter { $0.age > 18 } tableview.people = people tableview.reloadData() } catch { // Handle error } } }
  20. Interactors • Aka ViewModel in MVVM • Interacts with data

    providers • Performs business logic • Testable
  21. import Foundation struct PersonListInteractor { let personService: PersonService init(personService: PersonService)

    { self.personService = personService } func getAll() throws -> [Person] { return try personService.getAll() } func getWithAgeGreaterThan(_ age: Int16) throws -> [Person] { let people = try getAll() return people.filter { $0.age > 10 } } }
  22. class PersonListViewController: UIViewController { var personService: PersonService! private var personListInteractor:

    PersonListInteractor! override func viewDidLoad() { super.viewDidLoad() title = "People" personListInteractor = PersonListInteractor(personService: personService) do { let people = try personService.getWithAgeGreaterThan(18) tableview.people = people tableview.reloadData() } catch { // Handle error } } }
  23. class PersonListViewController: UIViewController { @IBAction func addPersonTapped(_ sender: Any) {

    let storyboard = UIStoryboard(name: "AddPerson", bundle: nil) let vc = storyboard.instantiateInitialViewController() as! AddPersonViewController vc.personService = personService vc.delegate = self navigationController?.pushViewController(vc, animated: true) } }
  24. class AddPersonViewController: UIViewController, AddPersonViewDelegate { func didAddPerson(_ person: Person) {

    do { try personService.createPerson(person) delegate?.didAddPerson(person) navigationController?.popViewController(animated: true) } catch { // Handle Error } } }
  25. protocol PersonListDelegate: class { func didTapAddPerson(_ sender: Any) } class

    PersonListViewController: UIViewController { @IBAction func addPersonTapped(_ sender: Any) { delegate?.didTapAddPerson(sender) } }
  26. import UIKit class PersonListCoordinator: Coordinator, PersonListDelegate { let presenter: UINavigationController

    let personService: PersonService private var personListViewController: PersonListViewController! private let addPersonCoordinator: AddPersonCoordinator init(presenter: UINavigationController, personService: PersonService) { self.presenter = presenter self.personService = personService addPersonCoordinator = AddPersonCoordinator( presenter: presenter, personService: personService ) } func start() { let storyboard = UIStoryboard(name: "PersonList", bundle: nil) personListViewController = storyboard.instantiateInitialViewController() as? PersonListViewController personListViewController.delegate = self personListViewController.personService = personService presenter.pushViewController(personListViewController, animated: true) addPersonCoordinator.delegate = self } func didTapAddPerson(_ sender: Any) { addPersonCoordinator.start() } }
  27. protocol AddPersonDelegate: class { func didAddPerson(_ person: Person) } class

    AddPersonViewController: UIViewController, AddPersonViewDelegate { func didAddPerson(_ person: Person) { do { try personService.createPerson(person) delegate?.didAddPerson(person) } catch { // Handle Error } } }
  28. import UIKit class AddPersonCoordinator: Coordinator, AddPersonDelegate { let presenter: UINavigationController

    let personService: PersonService private var addPersonViewController: AddPersonViewController! init(presenter: UINavigationController, personService: PersonService) { self.presenter = presenter self.personService = personService } func start() { let storyboard = UIStoryboard(name: "AddPerson", bundle: nil) addPersonViewController = storyboard.instantiateInitialViewController() as? AddPersonViewController addPersonViewController.delegate = self addPersonViewController.personService = personService presenter.pushViewController(addPersonViewController, animated: true) } func didAddPerson(_ person: Person) { presenter.popViewController(animated: true) } }
  29. import UIKit protocol AddPersonCoordinatorDelegate: class { func didAddPerson(_ person: Person)

    } class AddPersonCoordinator: Coordinator, AddPersonDelegate { weak var delegate: AddPersonCoordinatorDelegate! func didAddPerson(_ person: Person) { delegate?.didAddPerson(person) presenter.popViewController(animated: true) } }