New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A Taste of MVVM and RxSwift #120

Open
onmyway133 opened this Issue Dec 7, 2017 · 0 comments

Comments

Projects
None yet
1 participant
@onmyway133
Owner

onmyway133 commented Dec 7, 2017

This is about architectures, mvvm and RxSwift

Object Oriented Programming

  • characteristics 😬
  • action

I like Swift. Like many other object oriented programming languages, Swift allows you to represent real world objects that have some characteristics or can perform some work

Project Manager

I tend to think of an app as a world where each object is a person. They work and communicate. If you can’t do the work alone, you need to ask for help. What do we do in real world? We make apps. Let’s take a look at a project for example.

It can't be done alone by 1 person. If the manager has to do all of the work by himself, then he will go crazy, so he needs to organise and delegate the tasks. There are many persons collaborating on the project, designers, testers, scrum masters, developers, ...

  • Designer
  • Tester
  • Scrum Master
  • Developer -> Freelancer 🙃

You as the developer can even delegate the work to others if you know how to hire a freelancers to help you :] After each person has done his job, they need to report back to the manager, so he knows what to tell to the client.

This may not a good example. But at least you get the important of delegation and communication.

MVC

inline

Source https://www.raywenderlich.com

Now take a look at the architecture you know the best. MVC, short for Model View Controller. You get it for free when you create new project. View is where you present your data, using UIView, UIButton, UILabel, ... Model is just a fancy word for "your data". It can be your entity, data from Alamofire, object from Realm, or from Cache. The controller is the thing that mediates between the model and the view.

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

The problem with ViewController is it tends to be huge. Apple puts it as the center of the universe.

The ViewController has a lot of properties and responsibilities. There 's many things that you can only do with a UIViewController. . It can be used to interact with storyboard, manage the view, configure view rotation, manage state restoration, ...

Then you tend to put code into the ViewController to easily access its properties and methods, and what you gain is a big view controller.

If you put more stuff, like networking, caching, business logic, then you there's big chance that you can improve your scrolling skill.

VIPER

Short for View Interactor Presenter Entity Routing

So, what do you do when the ViewController is doing alot? You offset the work to others. If you want another object to do the user input handling, you can use the Presenter. If the Presenter is doing too much, then it can offset the business logic to the Interactor.

If the Interactor feels like it 's doing too much, then it can possibly delegate the network task to another object. Then you can get the VIPER architecture.

The buzzwords of architecture

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

Well, you can always create new classes, name it, and roll your own architectures. Just throw all the buzz words into action.

inline

Architecture

Characteristics of architectures according to me

  • Separation of concerns
  • Communication pattern
  • Comfortable to use

What I think is a good architecture. It should have clear separation of concerns and good communication pattern.

Each piece is identifiable, has a specific role. With good dependency injection, this makes testing easier.

The communication is clear. You know which object is talking to each other.

You and your team feel pleasant to work with. Always writing code as if you will be one who maintain it, then you will write it differently.

Saving private ViewController

To be honest, I tried ReSwift and VIPER. The idea is great, but somehow I feel very uncomfortable and I think it is not ready for iOS development yet. At least for me. Protocol is cool. Abstraction is cool. But too many of them is a problem.

So to save the ViewController from growing big. I think ViewModel is "good enough."

MVVM

inline

source https://www.raywenderlich.com

ViewModel is very simple. You move your code to another class, called ViewModel. So the iewController can say «Hey, ViewMode, do this for me. I don’t care what you do, just report when you’re done so that I have something to show»

It can do the calculation, formatting, call your API client there, or fetch data from cache. ViewModel is also a good place to do business logic.

  • Self contained
  • Testable
  • Good enough. Comfortable.
  • Improve existing ViewController

What I like about MVVM is it 's self contained. It has no reference to UIKit, it has just input and output. And the output is something the ViewController can consume directly. It is also testable.

And also, it is comfortable to work with. And you don’t need to rewrite the whole app, you can just apply it to one ViewController at a time.

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
  }
}

This is a very simple ViewModel. All it does is to formatting data based on User model. This is done synchronously.

Asynchronously

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

But we work with asynchronous API all the time. What if we want to show the user ’s number of Facebook friends? Well, for this to work, we need to call Facebook API, and this takes time. The ViewModel can report back via closure

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

Internally, the ViewModel may call the API Client to do the job.

There are many ways to communicate

  • KVO
  • Delegate
  • Notification
  • Closure

The ViewModel can report back to the ViewController through many ways. I myself prefer using Delegate and Closure as callback.

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
  }
}

If you want some fancy. You can declare a class called Binding and take advantage of property didSet. Whenever the value changes, it calls the listener

class ViewModel {
  let friends = Binding<[User]>(value: [])

  init() {
    getFacebookFriends {
      friends.value = $0
    }
  }

  func getFacebookFriends(completion: ([User]) -> Void) {
  	// Do the work
  }
}

Here is how you can expose friends as Binding

override func viewDidLoad() {
  super.viewDidLoad()

  viewModel.friends.bind { friends in
    self.friendsCountLabel.text = "\(friends.count)"
  }
}

When ever friends are fetched, or changed, the ViewController is updated automatically. This is reaction to changes. If you like reactive programming, then there's many frameworks to help you. I myself prefer using RxSwift.

RxSwift

So tip here is using RxSwift. You can call it R 10 Swift if you like. RxSwift is a Swift implementation of ReactiveX pattern, which allows you to do functional reactive programming. And it has brothers in many other languages, like Javascript and Kotlin. I learn RxSwift and I apply the same thing to my Nodejs project using RxJS. It is a great tool.

Observable

inline

Source http://rxmarbles.com/#filter

RxSwift unifies sync and async operations through Observable.

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()
    })
  }
}

This is how you make an Observable

override func viewDidLoad() {
  super.viewDidLoad()

  viewModel.friends.subscribe(onNext: { friends in
    self.friendsCountLabel.text = "\(friends.count)"
  })
}

Here you can just subscribe to the Observable, it will be triggered when the request completes

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())
}

The power of RxSwift lies in its numerous operators, which help you chain Observables. Here you can call 2 network requests, wait for both of them to finish, then sum up the friends. This is very streamlined, and saves you lots of time.

ViewController and View

Take a look Controller and View

The next thing, which I find interesting, if you're fan of doing things in code. To avoid ViewController from doing unnecessary view constructions and Auto Layout, and since View and ViewController is so coupled. You can make a generic ViewController. Thanks to my friend Vadym for showing this to me

class BaseViewController<V: UIView>: UIViewController {

	lazy var root: V = V()

	override func viewDidLoad() {
	  super.viewDidLoad()

	  view.addSubview(root)
	}
}
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)

This forces you to do all the view stuff inside your View, and not the ViewController, hence making the ViewController thinner.

Input Output

Take a look Input and output container

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
  }
}

Last but not least, when possible, you should construct your ViewModel to contain Input and Output. This makes things clearer.

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)"
    })
  }
}

Here you can tell which is input, which is output from the ViewModel

@onmyway133 onmyway133 referenced this issue Dec 7, 2017

Open

Talks #111

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment