GithubHelp home page GithubHelp logo

todolist-mvvm's Introduction

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!

todolist-mvvm's People

Contributors

jalehman avatar gabrielvieira avatar gbuela avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.