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

Progressive MVC Architecture

Progressive MVC Architecture

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