@GIORGIONATILI
FUNCTIONAL MODEL VIEW PRESENTER WITH RXSWIFT
WHO AM I? ▸ Technical Leader and Agile Coach· ▸ Front-end Developer (test first!) ▸ Mobile Developer (iOS, Android and hybrid with Cordova) ▸ Engineering Lead in McGraw-Hill Education ▸ Founder of Mobile Tea and New England Software Engineers ▸ Droidcon Boston organizer
AGENDA ▸ The risk of bringing a POC in production ▸ POC + iOS + MVC, a dangerous combination ▸ Model View Presenter ▸ Functional programming in a nutshell ▸ FRP, what the hell ?!? ▸ Using FRP with MVP
THE RISK OF BRINGING A POC IN PRODUCTION
LONG STORY SHORT
POTENTIAL ISSUES ▸ Code contains dirty hacks ▸ Code is written using the most convenient technologies rather than the most appropriates ▸ Code performances are tailored for dev machines ▸ Tests barely exist ▸ The design is not scalable
TECHNICAL DEBT ▸ Gets quickly outside of control ▸ Nobody really track it ▸ There is no time allocation to address it ▸ The “we’ll fix it!” attitude start to be part of the team core values
IOS SPECIFIC ISSUES WHEN DEVELOPING A POC
THE MASSIVE VIEW CONTROLLER ▸ Controllers are not just controllers (the class name should suggest this) ▸ Controllers are to much tied in the view (aka IBAction and IBOutlet) ▸ UI element s are configured in the viewDidLoad ▸ Location services, map services, etc. are normally used in the controller (delegates arena)
LACK OF CODE DESIGN AND STRUCTURE ▸ Features get added without any plan ▸ Conversation is the only specification ▸ Code smells subtly start to be part of your day by day life
LET’S ADD A SHAKE FEATURE TO AN IMAGE class FoodImageView: UIImageView { func shake() {
let animation = CABasicAnimation(keyPath: "position") animation.duration = 0.05 animation.repeatCount = 5 animation.autoreverses = true
}
}
animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 4.0, self.center.y)) animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 4.0, self.center.y)) layer.addAnimation(animation, forKey: "position")
NOTHING WRONG WITH THIS CODE, BUT… ▸ How add the same feature to other UIView subclasses? ▸ How communicate to others that the feature exists? ▸ How write semantic and clear code?
YOU CAN USE PROTOCOLS EXTENSIONS import UIKit protocol Shakeable { } extension Shakeable where Self: UIView {
}
func shake() { // implementation code }
PASSING DATA BETWEEN VIEWS override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) { if (segue.identifier == "segueTest") {
}
}
//Checking identifier is crucial as there might be multiple // segues attached to same view var detailVC = segue!.destinationViewController as DetailViewController; detailVC.toPass = Field.
▸ Not scalable ▸ Not maintainable
LET’S REVIEW THE CODE
MODEL VIEW PRESENTER
IN A NUTSHEL ▸ Greater separation of concerns ▸ Easier testability
HISTORY OF THE PATTERN ▸ Based on a generalization of the classic Smalltalk MVC ▸ Focused on decoupling the UI from the state of the app ▸ Designed to facilitate model composition and persistence ▸ Formalized by Taligent in 1996
MODEL VIEW PRESENTER IN DETAILS
THE VIEW IS PASSIVE ▸ A view react only to user inputs ▸ A view knows only how to render data ▸ A view can be easy tested
// MARK: - User interaction @IBAction func doLogin(sender: AnyObject) { } // MARK: - LoginView implementation func showErrorMessage(message: String) { }
CLEARLY DEFINED RELATIONSHIPS protocol LoginPresenter { var view: LoginView? { get set } var interactor: LoginInteractorInput? { get set } var router: LoginRouting? { get set }
}
/** * Communication VIEW -> PRESENTER * (how the presenter should be known from the view) */ func authenticate(username: String, _ password: String) func resetPassword(email: String)
protocol LoginView { var presenter: LoginPresenter? { get set }
}
/** * Communication PRESENTER -> VIEW * (how the view should be known from the presenter) */ func showErrorMessage(message: String) func showStatus(status: String) func enableInteraction(status: Bool)
TESTING THE VIEW (CONTROLLER) ▸ Indirectly testing the Presenter ▸ Testing the behavior of the UI
describe("Input field validation") { beforeEach { // Access the view to trigger LoginViewController.viewDidLoad(). let _ = viewController.view } it("It should render an error message when username and password are missing"){ viewController.login.sendActionsForControlEvents(UIControlEvents.TouchUpInside) expect(viewController.errorMessage.?.characters.count).toEventually(beGreaterThan(0), timeout: 1) } }
DEMO
FUNCTIONAL REACTIVE PROGRAMMING
FUNCTIONAL PROGRAMMING GLOSSARY ▸ Pure Functions ▸ When called with the same arguments, it always returns the same result ▸ There are no side effects, nothing mutated ▸ Lambdas are a shorthand version of a function declaration, often called “function literals” ▸ Currying allows you to partially supply parameters to a function so it can finish being filled in later.
IMPURE FUNCTIONS var a = 0 func f() -> Int {
}
a += 1 return a
let x = f() // 1 let y = f() // 2
FUNCTIONAL PROGRAMMING 101 IN SWIFT ▸ map, filter and reduce
let data = [1, 2, 3, 4, 5, 6] let sum = data.reduce(0, combine: {$0 + $1}) // 21 let tot = data.reduce(0, combine: +) // 21 let even = data.filter({$0 % 2 == 0}) // 2, 4, 6 let formatted = data.map {"\($0)$"} // 1$", "2$", "3$", "4$", "5$", ...
USING GENERICS IN SWIFT func quicksort(var elements: [T]) -> [T] { if elements.count > 1 {
} }
let pivot = elements.removeAtIndex(0) return quicksort(elements.filter { $0 <= pivot }) + [pivot] + quicksort(elements.filter { $0 > pivot })
return elements
FUNCTIONAL REACTIVE PROGRAMMING, WHAT THE HELL?!?
IN A NUTSHELL Reactive Programming is about propagating changes while declaring what to do to achieve a certain behavior when the given value changed, rather than how to achieve the desired behavior
▸ Stream ▸ Transformations ▸ Bindings
A SINGLE API TO RULE THEM ALL: REACTIVEX.IO
RXSWIFT - OBSERVABLES ▸ Observable is a generic class which conforms to ObservableType protocol ▸ Observable defines methods such as subscribe() and empty() ▸ Observables produce a stream of events
[1, 2, 3, 4, 5, 6] .toObservable() .subscribeNext { print($0) // 1 2 3 4 5 6 }
OBSERVABLE AND ARRAY [1, 2, 3, 4, 5, 6] .toObservable() .subscribeNext { print($0) // 1 2 3 4 5 6 }
OBSERVABLE OF MULTIPLE VALUES Observable.of("a", 2, 3, 4, 5) .subscribeNext { print($0) // a 1 2 3 4 5 } .dispose()
CLEAN ON DEINIT let disposeBag = DisposeBag() [1, 2, 3].toObservable() .subscribeNext { print($0) // 1 2 3 } .addDisposableTo(disposeBag)
MODIFIABLE OBSERVABLE SEQUENCE (BEHAVIORSUBJECT) ▸ It’s a sequence you can add new values on to ▸ The subscribers receive next events containing those newly added values ▸ BehaviorSubject is simply representing a value that is updated or changed over time let string = BehaviorSubject(value: "Hello") string.subscribe { print($0) // Next(Hello) Next(World!) } string.on(.Next("World!"))
RX VARIABLES ▸ A Variable is a wrapper around BehaviorSubject ▸ It exposes its BehaviorSubject’s observable sequence via the asObservable operator ▸ It is guaranteed to not emit error events, and it will emit a completed event when it’s about to be disposed of let number = Variable(1) number.asObservable() .subscribe { print($0) // Next(1) Next(12) Next(1234567) Completed } number.value = 12 number.value = 1_234_567
THAT’S JUST MAGIC!
NO, IT’S ALL ABOUT SIMPLICITY! class EmailValidationViewController: UIViewController { @IBOutlet weak var emailField: UIField! @IBOutlet weak var submitButton: UIButton! override func viewDidLoad() { super.viewDidLoad()
}
}
emailField.rx_ >- map (isEmail) >- submitButton.rx_subscribeEnabledTo
AND READABILITY! // MARK: - LoginInteractorInput implementation func aunthenticate(user: User) { services.auhtenticate(user).subscribe {
}
}
self.presenter?.loginDidSucceeded()
ADDITIONAL RESOURCES
AND LIBRARIES
RX EXTENSIONS (GITHUB.COM/RXSWIFTCOMMUNITY) ▸ RxCoreData
RxSwift extensions for Core Data ▸ RxAlamofire
Wrapper around the elegant HTTP networking in Swift Alamofire ▸ RxRealm
Wrapper for Realm's collection types ▸ RxCoreMotion
Provides an easy and straight-forward way to use Apple iOS _CoreMotion_ responses as Rx Observables. ▸ RxSegue
LINKS AND TUTORIALS ▸ Couple of misconceptions to play with https://sideeffects.xyz/ 2015/the-functional-reactive-misconception/ ▸ The simplicity of functional reactive programming syntax https://realm.io/news/nacho-soto-functional-reactiveprogramming/ ▸ ReSwift in a nutshell https://realm.io/news/benji-enczunidirectional-data-flow-swift ▸ Redux for Swift http://reswift.github.io/ReSwift/master/gettingstarted-guide.html
THANKS!