All Projects → jalehman → Todolist Mvvm

jalehman / Todolist Mvvm

Licence: mit
Sample application using MVVM in Swift

Programming Languages

swift
15916 projects

#+TITLE: MVVM on iOS #+EMAIL: [email protected] #+REVEAL_ROOT: https://cdn.jsdelivr.net/reveal.js/3.0.0/ #+OPTIONS: toc:nil reveal_slide_number:nil num:nil #+REVEAL_TRANS: linear

  • MVC: Model-View-Controller

[[./img/mvc.png]]

  • Architectural design pattern
  • Developed by Xerox PARC in the 1970s
  • Expressed as a general concept in 1988

** It's an old concept

  • Which is not bad
  • Good ideas are improved upon over time
  • MVVM: Model-View-ViewModel

[[./img/mvvm.png]]

  • Developed by Microsoft, announced in 2005
  • A slight addition to MVC
  • We'll discuss Data Binding later

** Adds the /ViewModel/

#+begin_quote "The central component of MVC, the model, captures the behavior of the application in terms of its problem domain, independent of the user interface."

-- [[https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller#Components][Wikipedia]] #+end_quote

/(where user interface is the View and Controller)/

#+REVEAL: split

#+begin_quote The =ViewModel= captures the /behaviors/ of an user interface in terms of general user interactions, independent of the view itself. #+end_quote

** Why is this a good thing?

[[./img/massive-view-controller.png]]

  • Smaller view controllers!
  • Lower coupling
    • Decouples GUI code from presentation logic and state
  • Headless testing

** View and ViewModel Relationship

  • /Generally/, one =ViewModel= per controller or =UIView= subclass:
    • =UIViewController=
    • =UITableViewCell=
    • =UICollectionViewCell=
    • etc.

** Ownership

[[./img/mvvm.png]]

  • The View /owns/ the ViewModel
    • ViewModels know /nothing/ about Views
  • The ViewModel /owns/ the Model
    • Models know /nothing/ about ViewModels
  • The View knows /nothing/ about the Model

** Views do not communicate!

Views communicate with their ViewModels, which communicate with each other. ** Problem

If the view's state is stored in a =ViewModel= class, how do we keep the two in sync?

** Data Binding

  • Not strictly /necessary/, but really helpful
    • Delegates can work here, but are more verbose
  • Helps keep =ViewModel= in sync with its =View=

*** 1-way > 2-way

  • 2-way binding is really hard (it's cyclical)
    • If 2-way binding seems like the only solution, find a better solution

*** [[https://github.com/ReactiveCocoa/ReactiveCocoa][ReactiveCocoa]] (RAC)

  • Mostly based on [[https://en.wikipedia.org/wiki/Functional_reactive_programming][Functional Reactive Programming (FRP)]]
    • Represent data as "streams of values over time"
  • Used at: [[http://savvy.ai][Savvy]], Snapchat, GitHub, probably some other places too

*** Popular and well-maintained

  • First released :: 2/26/2012
  • Last commit to master :: 11/3/2015 (at time of writing)
  • Stars :: 11,081
  • Contributors :: 129

*** [[https://github.com/SwiftBond/Bond][Bond]]

  • A data binding framework
  • Less concept-heavy
  • Also well maintained
  • I am less familiar with it -- examples will use RAC
  • Interlude: ReactiveCocoa

** What is "Functional Reactive Programming"?

#+begin_quote Functional reactive programming (FRP) is a programming paradigm for reactive programming (asynchronous dataflow programming) using the building blocks of functional programming (e.g. map, reduce, filter). #+end_quote

** Signals

  • Represent streams of values (data) as they change
  • Signals can be /observed/
  • Two varieties in RAC: =SignalProducer= and =Signal=
  • Send /events/:
    • =next=: The data that the signal carries -- can happen many times
    • =error=: An error occurred -- terminates
    • =interrupted=: The signal was interrupted -- terminates
    • =completed=: Successful completion -- terminates

** Signal Producers

#+begin_src swift func doNetworkStuff() -> SignalProducer<JSON, NoError> let producer = doNetworkStuff() producer.startWithNext { json in print(json) } #+end_src

  • Has to be "started" to do anything
  • Kind of like promises
  • Network requests are a good example

** Signals

  • Send values regardless of whether or not anything is observing
  • "Always On" semantics

** Mutable Properties

#+begin_src swift let text = MutableProperty("Hello, World!") text.value // => "Hello, World!" text.producer // => SignalProducer<String, NoError> text.producer.startWithNext { s in print(s) } // prints "Hello, World!" text.value = "Yo." // prints "Yo" #+end_src

  • Exposes a =SignalProducer= of the values in the property

** Binding

#+begin_src swift let (producer, observer) = SignalProducer<String, NoError>.buffer() let text = MutableProperty("") text <~ producer observer.sendNext("a") text.value // "a" observer.sendNext("b") text.value // "b" #+end_src

  • We can /bind/ the result of a =SignalProducer= to a =MutableProperty=
  • The binding operator: =<~=
  • No =KVO=!

** Actions

#+begin_src swift func saveTodoOnServer(todo: Todo) -> SignalProducer<Bool, NSError> { return SignalProducer(value: true) } let createTodo = Action { (t: Todo) -> SignalProducer<Bool, NSError> in return saveTodoOnServer(t) } let todo = Todo() createTodo.values.observeNext { success in print(success) } createTodo.apply(todo) // => SignalProducer<Bool, NSError> createTodo.apply(todo).start() // prints "true" createTodo.apply(todo).start() // prints "true" #+end_src

  • Like a function, but where the result of invocation is /observed/ rather than /returned/
    • Can have many observers!
  • Take parameters, return a =SignalProducer=
    • We /apply/ parameters, and then /start/ the resulting producer
    • Expose =values= property: A =Signal= of the values of the =SignalProducer=
  • A Sample Application: Todo List

** ViewModels Drive the Application

#+begin_src swift protocol ViewModelServicesProtocol {

var todo: TodoServiceProtocol { get }
var date: DateServiceProtocol { get }

func push(viewModel: ViewModelProtocol)
func pop(viewModel: ViewModelProtocol)

}

protocol ViewModelProtocol { var services: ViewModelServicesProtocol { get } } #+end_src

*** Navigation

#+begin_src swift func push(viewModel: ViewModelProtocol) func pop(viewModel: ViewModelProtocol) #+end_src

  • ViewModels will instantiate and =push= other ViewModels.
  • Services are responsible for instantiating the proper Views.

*** Model Services

#+begin_src swift protocol TodoServiceProtocol { func update(todo: Todo) -> SignalProducer<Todo, NoError> func delete(todo: Todo) -> SignalProducer<Bool, NoError> func create(note: String, dueDate: NSDate) -> SignalProducer<Todo, NoError> } #+end_src

  • Model services deal with stateful resources, e.g. network operations
  • Only ViewModels have access to services

** Views Observe ViewModels and /React/

#+begin_src swift class TodoTableViewModel: ViewModel, CreateTodoViewModelDelegate { let todos = MutableProperty<[TodoCellViewModel]>([]) let deleteTodo: Action<(todos: [TodoCellViewModel], cell: TodoCellViewModel), NSIndexPath?, NoError> } class TodoTableViewController: ReactiveViewController { override func viewDidLoad() { super.viewDidLoad() func removeRow(indexPath: NSIndexPath?) { todoTableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Left) } // Remove a row whenever a Todo is deleted viewModel.deleteTodo.values .filter { $0 != nil } .observeOn(UIScheduler()) .observeNext(removeRow) } } #+end_src

** Demo

The code: https://github.com/jalehman/todolist-mvvm

  • Inspiration & Credits
  • [[http://blog.scottlogic.com/ceberhardt/][Colin Eberhardt]]'s series of tutorials on MVVM and RAC
  • [[https://realm.io/news/andy-matuschak-controlling-complexity][Controlling Complexity in Swift]] by Andy Matuschak
  • [[https://www.youtube.com/watch?v=7AqXBuJOJkY][Enemy of the State]] by Justin Spahr-Summers
  • Wikipedia

** Mobile Makers

Thanks for letting me talk!

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].