A taste of MVVM and Reactive paradigm

Model View Controller

UIViewController is the center of the universe

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

The pragmatic programmer

Don’t fight against the system

Model View ViewModel

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

Asynchronously

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

Jetpack in Android

class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
val model = ViewModelProviders.of(this).get(MyViewModel::class.java)
model.getUsers().observe(this, { users ->
// update UI
})
}
}

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
}
}
class ViewModel {
let friends = Binding<[User]>(value: [])
init() {
getFacebookFriends {
friends.value = $0
}
}
func getFacebookFriends(completion: ([User]) -> Void) {
// Do the work
}
}
override func viewDidLoad() {
super.viewDidLoad()
viewModel.friends.bind { friends in
self.friendsCountLabel.text = "\(friends.count)"
}
}

RxSwift

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

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

How reactive works

Monad

Sync vs Async

// Sync
func sum(a: Int, b: Int) -> Int {
return a + b
}
// Async
func sum(a: Int, b: Int, completion: Int -> Void) {
// Assumed it is a very long task to get the result
let result = a + b
completion(result)
}
enum Result<T> {
case value(value: T)
case failure(error: Error)
// Sync
public func map<U>(f: (T) -> U) -> Result<U> {
switch self {
case let .value(value):
return .value(value: f(value))
case let .failure(error):
return .failure(error: error)
}
}
// Async
public func map<U>(f: @escaping ((T), (U) -> Void) -> Void) -> (((Result<U>) -> Void) -> Void) {
return { g in // g: Result<U> -> Void
switch self {
case let .value(value):
f(value) { transformedValue in // transformedValue: U
g(.value(value: transformedValue))
}
case let .failure(error):
g(.failure(error: error))
}
}
}
}

Push Signal

public final class PushSignal<T> {
var event: Result<T>?
var callbacks: [(Result<T>) -> Void] = []
let lockQueue = DispatchQueue(label: "Serial Queue")
func notify() {
guard let event = event else {
return
}
callbacks.forEach { callback in
callback(event)
}
}
func update(event: Result<T>) {
lockQueue.sync {
self.event = event
}
notify()
}
public func subscribe(f: @escaping (Result<T>) -> Void) -> Signal<T> {
// Callback
if let event = event {
f(event)
}
callbacks.append(f) return self
}
public func map<U>(f: @escaping (T) -> U) -> Signal<U> {
let signal = Signal<U>()
_ = subscribe { event in
signal.update(event: event.map(f: f))
}
return signal
}
}
let signal = PushSignal<String>()_ = signal.map { value in
return value.count
}.subscribe { event in
if case let .value(value) = event {
print(value)
} else {
print("error")
}
}
signal.update(event: .value(value: "test"))

Pull Signal

public struct PullSignal<T> {
let operation: ((Result<T>) -> Void) -> Void
public init(operation: @escaping ((Result<T>) -> Void) -> Void) {
self.operation = operation
}
public func start(completion: (Result<T>) -> Void) {
operation() { event in
completion(event)
}
}
public func map<U>(f: @escaping (T) -> U) -> PullSignal<U> {
return PullSignal<U> { completion in
self.start { event in
completion(event.map(f: f))
}
}
}
}
let signal = PullSignal<String> { completion in
// There should be some long running operation here
completion(Result.value(value: "test"))
}
signal.map { value in
value.count
}.start { event in
if case let .value(value) = event {
print(value)
} else {
print("error")
}
}

Where to go from here

Check out my apps https://onmyway133.com/apps

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

How to publish an Expo SDK App on Ios without a Mac

My iOS Developer Story

How to use flexible frame in SwiftUI

Junit 5 Asserts Exceptions

Recording Case Information

4. Connect React Native to C++

Rubic & Swift Finance Partnership!

RxDataSource : Scroll to bottom

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Khoa Pham

Khoa Pham

Check out my apps https://onmyway133.com/apps

More from Medium

Xcode-How to change the main Storyboard

Swift — Ignore safe area when using UIScrollView

Access specifiers in Swift

RxSwift Combination Operators: zip, combineLatest & withLatestFrom