A Taste of MVVM + RxSwift

16bebb36e0e28572a316ba0450e190d1?s=47 Khoa Pham
November 25, 2017

A Taste of MVVM + RxSwift

My talk at Mobile Era 2016

16bebb36e0e28572a316ba0450e190d1?s=128

Khoa Pham

November 25, 2017
Tweet

Transcript

  1. A TASTE OF MVVM + RXSWIFT

  2. aka THE 100TH TALK ABOUT MVVM, RXSWIFT ! WHY DO

    PEOPLE KEEP TALKING ABOUT THIS ?
  3. ABOUT > khoa@hyper.no > https://github.com/onmyway133 > https://github.com/hyperoslo

  4. OBJECT ORIENTED PROGRAMMING > characteristics > action

  5. PROJECT MANAGER > Designer > Tester > Scrum Master >

    Developer -> Freelancer
  6. MVC Source https://www.raywenderlich.com

  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 ...
  8. VIPER View Interactor Presenter Entity Routing

  9. FCPN Functional Controller Presenter Navigator

  10. CRSC Clean Reactor Store Coordinator

  11. MRRI Megatron Router Reactive Interactor

  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()
  13. None
  14. ARCHITECTURE > Separation of concerns > Communication pattern > Comfortable

    to use
  15. SAVING PRIVATE VIEWCONTROLLER

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

  17. MVVM > Self contained > Testable > Good enough. Comfortable.

    > Improve existing ViewController
  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 } }
  19. asynchronously viewModel.getFacebookFriends { friends in self.friendCountLabel.text = "\(friends.count)" }

  20. class ViewModel { func getFacebookFriends(completion: [User] -> Void) { let

    client = APIClient() client.getFacebookFriends(for: user) { friends in DispatchQueue.main.async { completion(friends) } } } }
  21. > KVO > Delegate > Notification > Closure

  22. BINDING class Binding<T> { 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 } }
  23. class ViewModel { let friends = Binding<[User]>(value: []) init() {

    getFacebookFriends { friends.value = $0 } } func getFacebookFriends(completion: ([User]) -> Void) { // Do the work } }
  24. override func viewDidLoad() { super.viewDidLoad() viewModel.friends.bind { friends in self.friendsCountLabel.text

    = "\(friends.count)" } }
  25. RXSWIFT https://github.com/ReactiveX/RxSwift

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

  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() }) } }
  28. override func viewDidLoad() { super.viewDidLoad() viewModel.friends.subscribe(onNext: { friends in self.friendsCountLabel.text

    = "\(friends.count)" }) }
  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()) }
  30. VIEWCONTROLLER AND VIEW class BaseViewController<V: UIView>: UIViewController { lazy var

    root: V = V() override func viewDidLoad() { super.viewDidLoad() view.addSubview(root) } }
  31. class ProfileView: UIView { // Construct subviews and do Auto

    Layout here } class ProfileViewController: BaseViewController<ProfileView> { // Handle actions from view } let profileVC = ProfileViewController() navigationController.pushViewController(profileVC, animated: true)
  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 } }
  33. class ProfileViewController: BaseViewController<ProfileView> { 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)" }) } }
  34. Q & A