Skip to content

Latest commit

 

History

History
286 lines (204 loc) · 7.64 KB

readme.org

File metadata and controls

286 lines (204 loc) · 7.64 KB

MVVM on iOS

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

“The central component of MVC, the model, captures the behavior of the application in terms of its problem domain, independent of the user interface.”

Wikipedia

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

The ViewModel captures the behaviors of an user interface in terms of general user interactions, independent of the view itself.

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

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
  • 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”?

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

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

func doNetworkStuff() -> SignalProducer<JSON, NoError>
let producer = doNetworkStuff()
producer.startWithNext { json in print(json) }
  • 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

let text = MutableProperty<String>("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"
  • Exposes a SignalProducer of the values in the property

Binding

let (producer, observer) = SignalProducer<String, NoError>.buffer()
let text = MutableProperty<String>("")
text <~ producer
observer.sendNext("a")
text.value // "a"
observer.sendNext("b")
text.value // "b"
  • We can bind the result of a SignalProducer to a MutableProperty
  • The binding operator: <~
  • No KVO!

Actions

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"
  • 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

protocol ViewModelServicesProtocol {

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

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

protocol ViewModelProtocol {
    var services: ViewModelServicesProtocol { get }
}

Navigation

func push(viewModel: ViewModelProtocol)
func pop(viewModel: ViewModelProtocol)
  • ViewModels will instantiate and push other ViewModels.
  • Services are responsible for instantiating the proper Views.

Model Services

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>
}
  • Model services deal with stateful resources, e.g. network operations
  • Only ViewModels have access to services

Views Observe ViewModels and React

class TodoTableViewModel: ViewModel, CreateTodoViewModelDelegate {
    let todos = MutableProperty<[TodoCellViewModel]>([])
    let deleteTodo: Action<(todos: [TodoCellViewModel], cell: TodoCellViewModel), NSIndexPath?, NoError>
}
class TodoTableViewController: ReactiveViewController<TodoTableViewModel> {
    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)
    }
}

Demo

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

Inspiration & Credits

Mobile Makers

Thanks for letting me talk!