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 Slide

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

    View Slide

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

    View Slide

  4. OBJECT ORIENTED PROGRAMMING
    > characteristics
    > action

    View Slide

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

    View Slide

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

    View 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 Slide

  8. VIPER
    View Interactor Presenter Entity Routing

    View Slide

  9. FCPN
    Functional Controller Presenter Navigator

    View Slide

  10. CRSC
    Clean Reactor Store Coordinator

    View Slide

  11. MRRI
    Megatron Router Reactive Interactor

    View 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 Slide

  13. View Slide

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

    View Slide

  15. SAVING PRIVATE VIEWCONTROLLER

    View Slide

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

    View Slide

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

    View Slide

  18. 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 Slide

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

    View Slide

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

    View Slide

  21. > KVO
    > Delegate
    > Notification
    > Closure

    View Slide

  22. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  27. 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 Slide

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

    View Slide

  29. 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 Slide

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

    View Slide

  31. 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 Slide

  32. 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 Slide

  33. 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 Slide

  34. Q & A

    View Slide