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

A Taste of MVVM + RxSwift

Khoa Pham
November 25, 2017

A Taste of MVVM + RxSwift

My talk at Mobile Era 2016

Khoa Pham

November 25, 2017
Tweet

More Decks by Khoa Pham

Other Decks in Programming

Transcript

  1. A TASTE OF
    MVVM +
    RXSWIFT

    View full-size slide

  2. aka
    THE 100TH TALK ABOUT MVVM, RXSWIFT
    !
    WHY DO PEOPLE KEEP TALKING ABOUT
    THIS ?

    View full-size slide

  3. ABOUT
    > [email protected]
    > https://github.com/onmyway133
    > https://github.com/hyperoslo

    View full-size slide

  4. OBJECT ORIENTED PROGRAMMING
    > characteristics
    > action

    View full-size slide

  5. PROJECT MANAGER
    > Designer
    > Tester
    > Scrum Master
    > Developer -> Freelancer

    View full-size slide

  6. MVC
    Source https://www.raywenderlich.com

    View full-size slide

  7. UIVIEWCONTROLLER
    func viewDidLoad()
    var preferredStatusBarStyle: UIStatusBarStyle { get }
    UITableViewDataSource
    var presentationController: UIPresentationController? { get }
    func childViewControllerForScreenEdgesDeferringSystemGestures() -> UIViewController?
    func didMove(toParentViewController parent: UIViewController?)
    var systemMinimumLayoutMargins: NSDirectionalEdgeInsets
    var edgesForExtendedLayout: UIRectEdge
    var previewActionItems: [UIPreviewActionItem]
    var navigationItem: UINavigationItem
    var shouldAutorotate: Bool
    ...

    View full-size slide

  8. VIPER
    View Interactor Presenter Entity Routing

    View full-size slide

  9. FCPN
    Functional Controller Presenter Navigator

    View full-size slide

  10. CRSC
    Clean Reactor Store Coordinator

    View full-size slide

  11. MRRI
    Megatron Router Reactive Interactor

    View full-size slide

  12. let buzzWords = [
    "Model", "View", "Controller", "Entity", "Router", "Clean", "Reactive",
    "Presenter", "Interactor", "Megatron", "Coordinator", "Flow", "Manager"
    ]
    let architecture = buzzWords.shuffled().takeRandom()
    let acronym = architecture.makeAcronym()

    View full-size slide

  13. ARCHITECTURE
    > Separation of concerns
    > Communication pattern
    > Comfortable to use

    View full-size slide

  14. SAVING PRIVATE VIEWCONTROLLER

    View full-size slide

  15. MVVM
    source https://www.raywenderlich.com

    View full-size slide

  16. MVVM
    > Self contained
    > Testable
    > Good enough. Comfortable.
    > Improve existing ViewController

    View full-size slide

  17. synchronously
    class ProfileController: UIViewController {
    override func viewDidLoad() {
    super.viewDidLoad()
    let viewModel = ViewModel(user: user)
    nameLabel.text = viewModel.name
    birthdayLabel.text = viewModel.birthdayString
    salaryLabel.text = viewModel.salary
    piLabel.text = viewModel.millionthDigitOfPi
    }
    }

    View full-size slide

  18. asynchronously
    viewModel.getFacebookFriends { friends in
    self.friendCountLabel.text = "\(friends.count)"
    }

    View full-size slide

  19. class ViewModel {
    func getFacebookFriends(completion: [User] -> Void) {
    let client = APIClient()
    client.getFacebookFriends(for: user) { friends in
    DispatchQueue.main.async {
    completion(friends)
    }
    }
    }
    }

    View full-size slide

  20. > KVO
    > Delegate
    > Notification
    > Closure

    View full-size slide

  21. BINDING
    class Binding {
    var value: T {
    didSet {
    listener?(value)
    }
    }
    private var listener: ((T) -> Void)?
    init(value: T) {
    self.value = value
    }
    func bind(_ closure: @escaping (T) -> Void) {
    closure(value)
    listener = closure
    }
    }

    View full-size slide

  22. class ViewModel {
    let friends = Binding<[User]>(value: [])
    init() {
    getFacebookFriends {
    friends.value = $0
    }
    }
    func getFacebookFriends(completion: ([User]) -> Void) {
    // Do the work
    }
    }

    View full-size slide

  23. override func viewDidLoad() {
    super.viewDidLoad()
    viewModel.friends.bind { friends in
    self.friendsCountLabel.text = "\(friends.count)"
    }
    }

    View full-size slide

  24. RXSWIFT
    https://github.com/ReactiveX/RxSwift

    View full-size slide

  25. OBSERVABLE
    Source http://rxmarbles.com/#filter

    View full-size slide

  26. class ViewModel {
    let friends: Observable<[User]>
    init() {
    let client = APIClient()
    friends = Observable<[User]>.create({ subscriber in
    client.getFacebookFriends(completion: { friends in
    subscriber.onNext(friends)
    subscriber.onCompleted()
    })
    return Disposables.create()
    })
    }
    }

    View full-size slide

  27. override func viewDidLoad() {
    super.viewDidLoad()
    viewModel.friends.subscribe(onNext: { friends in
    self.friendsCountLabel.text = "\(friends.count)"
    })
    }

    View full-size slide

  28. let facebookFriends: Observable<[User]> // network request
    let twitterFriends: Observable<[User]> // network request
    let totalFriends: Observable<[User]> = Observable.combineLatest([facebookFriends, twitterFriends]) { friends in
    return Array(friends.joined())
    }

    View full-size slide

  29. VIEWCONTROLLER AND VIEW
    class BaseViewController: UIViewController {
    lazy var root: V = V()
    override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(root)
    }
    }

    View full-size slide

  30. class ProfileView: UIView {
    // Construct subviews and do Auto Layout here
    }
    class ProfileViewController: BaseViewController {
    // Handle actions from view
    }
    let profileVC = ProfileViewController()
    navigationController.pushViewController(profileVC, animated: true)

    View full-size slide

  31. INPUT OUTPUT
    class ViewModel {
    class Input {
    let fetch = PublishSubject<()>()
    }
    class Output {
    let friends: Driver<[User]>
    }
    let apiClient: APIClient
    let input: Input
    let output: Output
    init(apiClient: APIClient) {
    self.apiClient = apiClient
    // Connect input and output
    }
    }

    View full-size slide

  32. class ProfileViewController: BaseViewController {
    let viewModel: ProfileViewModelType
    init(viewModel: ProfileViewModelType) {
    self.viewModel = viewModel
    }
    override func viewDidLoad() {
    super.viewDidLoad()
    // Input
    viewModel.input.fetch.onNext(())
    // Output
    viewModel.output.friends.subscribe(onNext: { friends in
    self.friendsCountLabel.text = "\(friends.count)"
    })
    }
    }

    View full-size slide