reactive thinking in ios development - pedro piñera buendía - codemotion amsterdam 2016
TRANSCRIPT
REACTIVE THINKING IN IOS DEVELOPMENT
@PEPIBUMUR / @SAKY
WHO?
@pepibumuriOS Developer at SoundCloud
GitHub: pepibumurTwitter: pepibumur
@sakyiOS Developer at Letgo
GitHub: isaacroldanTwitter: saky
GitDo.io our spare time project
INDEX
> Programming Paradigms> Reactive Libraries
> Reactive Motivation> Reactive Thinking> Reactive Caveats
> Conclusion
PARADIGMS !WAYS OF SEEING THE WORLD WHEN IT COMES TO PROGRAMMING
WIKIPEDIA
Data-Driven, Declarative, Dynamic, End-User, Event-Driven, Expression-Oriented, Feature-Oriented, Function-level,
Generic, Imperative, Inductive, Language Oriented, Metaprogramming, Non-Structured, Nondeterministic,
Parallel computing, Point-free Style, Structured, Value-Level, Probabilistic
IMPERATIVE PROGRAMMINGDECLARATIVE PROGRAMMING
IMPERATIVE PROGRAMMINGDECLARATIVE PROGRAMMING
HOWSEQUENCE OF STEPS
THAT HAPPEN IN ORDER
NATURAL WAY TO PROGRAM
EXECUTION STATE(AKA SIDE EFFECT)
IMPERATIVE PROGRAMMING
func userDidSearch(term: String) { let apiReults = api.search(term: term).execute() self.items = self.adaptResults(apiResults) self.tableView.reloadData()}
IMPERATIVE PROGRAMMINGDECLARATIVE PROGRAMMING
WHATIT DOESN'T DESCRIBE THE
CONTROL FLOW
HTML
HTMLSQL
HTMLSQL
REACTIVE PROGRAMMING
DECLARATIVE PROGRAMMING
let predicate = NSPredicate(format: "name == %@", "Pedro")let regex = NSRegularExpression(pattern: ".+", options: 0)
REACTIVE PROGRAMMINGDATA-FLOW PROGRAMMING
(DESCRIBES THE STATE PROPAGATION)
# THE INTRODUCTION TO REACTIVE PROGRAMMING THAT YOU'VE BEEN MISSING
FUNCTIONAL REACTIVE PROGRAMMING
(AKA FRP)
REACTIVE LIBRARIES
> RxSwift (ReactiveX)> ReactiveCocoa> BrightFutures
> ReactKit> Bond
> More and more... PromiseKit, Bolts...
WHAT LIBRARY SHOULD I USE? !
WHAT LIBRARY SHOULD I USE? !DO I NEED A LIBRARY FOR THIS? !
SWIFT
var userName: String { didSet { // React to changes in variables
}}
SWIFT
var userName: String { didSet { // React to changes in variables view.updateTitle(userName) }}
MOTIVATION !WHY SHOULD I STICK TO REACTIVE PROGRAMMING?
> Bindings> Composability
> Threading
> Bindings> Composability
> Threading
BINDING
UI BINDING
UI BINDING
UI BINDING
> Bindings> Composability
> Threading
> Bindings> Composability
> Threading
> Bindings> Composability
> Threading (observer and execution)
THREADING
REACTIVE THINKING !
THINKING IN TERMS OF
OBSERVABLESOR SIGNALS/PRODUCERS IN REACTIVECOCOA
ACTIONS CAN BE
OBSERVEDHOW? !
.NEXT(T).ERROR(ERRORTYPE)
.COMPLETE
OPERATIONS
RxSwiftObservable<String>.create { (observer) -> Disposable in observer.onNext("next value") observer.onCompleted() return NopDisposable.instance // For disposing the action}
ReactiveCocoaSignalProducer<String>.create { (observer, disposable) in observer.sendNext("next value") observer.sendComplete()}
EXISTING PATTERNSRXSWIFT ▶︎ RXCOCOA (EXTENSIONS)
REACTIVECOCOA ▶︎ DO IT YOURSELF
UIKITlet button = UIButton()button.rx_controlEvent(.TouchUpInside) .subscribeNext { _ in print("The button was tapped") }
NOTIFICATIONSNSNotificationCenter.defaultCenter() .rx_notification("my_notification", object: nil) .subscribeNext { notification // We got a notification }
DELEGATESself.tableView.rx_delegate // DelegateProxy .observe(#selector(UITableViewDelegate.tableView(_:didSelectRowAtIndexPath:))) .subscribeNext { (parameters) in // User did select cell at index path }
PLAYING !WITH OBSERVABLES
OBSERVABLE
let tracksFetcher = api.fetchTracks // Background .asObservable()
ERROR HANDLING
let tracksFetcher = api.fetchTracks // Background .asObservable() .retry(3) .catchErrorJustReturn([])
MAPPING
let tracksFetcher = api.fetchTracks // Background .asObservable() .retry(3) .catchErrorJustReturn([]) .map(TrackEntity.mapper().map)
FILTERING
let tracksFetcher = api.fetchTracks // Background .asObservable() .retry(3) .catchErrorJustReturn([]) .map(TrackEntity.mapper().map) .filter { $0.name.contains(query) }
FLATMAPPING
let tracksFetcher = api.fetchTracks // Background .asObservable() .retry(3) .catchErrorJustReturn([]) .map(TrackEntity.mapper().map) .filter { $0.name.contains(query) } .flatMap { self.rx_trackImage(track: $0) }
OBSERVATION THREAD
let tracksFetcher = api.fetchTracks // Background .asObservable() .retry(3) .catchErrorJustReturn([]) .map(TrackEntity.mapper().map) .filter { $0.name.contains(query) } .flatMap { self.rx_trackImage(track: $0) } .observeOn(MainScheduler.instance) // Main thread
LE IMPERATIVE WAY !"func fetchTracks(retry retry: Int = 0, retryLimit: Int, query: query, mapper: (AnyObject) -> Track, completion: ([UIImage], Error?) -> ()) { api.fetchTracksWithCompletion { (json, error) in if let _ = error where retry < retryLimit { fetchTracksWithRetry(retry: retry+1, retryLimit: retryLimit, query: query, mapper: mapper, completion: completion) } else if let error = error { completion([], error) } else if let json = json { guard let jsonArray = json as? [AnyObject] else { completion([], Error.InvalidResponse) return } let mappedTracks = jsonArray.map(mapper) let filteredTracks = mappedTracks.filter { $0.name.contains(query) } self.fetchImages(track: filteredTracks, completion: ([])) let trackImages = self.fetchImages(tracks: filteredTracks, completion: { (images, error) in dispatch_async(dispatch_get_main_queue(),{ if let error = error { completion([], error) return } completion(images, error) }) }) } }}
THROTTLINGCLASSIC REACTIVE EXAMPLE
func tracksFetcher(query: String) -> Observable<[TrackEntity]>
searchTextField .rx_text.throttle(0.5, scheduler: MainScheduler.instance) .flatmap(tracksFetcher) .subscribeNext { tracks in // Yai! Tracks searched }
OTHER OPERATORS
Combining / Skipping Values / Deferring / Concatenation / Take some values / Zipping
OBSERVING !EVENTS
SUBSCRIBING
observable subscribe { event switch (event) { case .Next(let value): print(value) case .Completed: print("completed") case .Error(let error): print("Error: \(error)") } }
BIND CHANGES OVER THE TIME TO AN OBSERVABLE
OBSERVABLE ▶ BINDING ▶ OBSERVER
BINDING
To a Variablelet myVariable: Variable<String> = Variable("")observable .bindTo(myVariable)print(myVariable.value)
To UIobservable .bindTo(label.rx_text)
! CAVEATSBECAUSE YES...
IT COULDN'T BE PERFECT
DEBUGGINGDEBUG OPERATOR IN RXSWIFT
let tracksFetcher = api.fetchTracks // Background .asObservable() .retry(3) .catchErrorJustReturn([]) .map(TrackEntity.mapper().map) .filter { $0.name.contains(query) } .flatMap { self.rx_trackImage(track: $0) } .observeOn(MainScheduler.instance) // Main thread
let tracksFetcher = api.fetchTracks // Background .asObservable .debug("after_fetch") // <-- Debugging probes .retry(3) .catchErrorJustReturn([]) .map(TrackEntity.mapper().map) .debug("mapped_results") // <-- Debugging probes .filter { $0.name.contains(query) } .flatMap { self.rx_trackImage(track: $0) } .observeOn(MainScheduler.instance) // Main thread
//let tracksFetcher = api.fetchTracks // Background// .asObservable .debug("after_fetch") // <-- Debugging probes// .retry(3)// .catchErrorJustReturn([])// .map(TrackEntity.mapper().map) .debug("mapped_results") // <-- Debugging probes// .filter { $0.name.contains(query) }// .flatMap { self.rx_trackImage(track: $0) }// .observeOn(MainScheduler.instance) // Main thread
> [after_fetch] Next Event... // Downloaded tracks> [mapped_results] Error ... // Detected error after mapping> [...]
RETAIN CYCLES
class IssuePresenter { var disposable: Disposable
func fetch() { self.disposable = issueTitle .observable() .bindTo { self.titleLabel.rx_text } }}
UNSUBSCRIPTIONYOU NEED TO TAKE CARE OF THE
LIFECYCLE OF YOUR OBSERVABLES
UNSUBSCRIPTION
title.asObservable().bindTo(field.rx_text)
// RxSwift will show a warning
UNSUBSCRIPTION
let titleDisposable = title.asObservable().bindTo(field.rx_text)titleDisposable.dispose()
UNSUBSCRIPTION
let disposeBag = DisposeBag()
title.asObservable() .bindTo(field.rx_text) .addDisposableTo(disposeBag)
// The binding is active until the disposeBag is deallocated
CONCLUSIONS
PREVENTS STATEFUL CODE
DATA FLOW MANIPULATION BECOMES EASIER
BUT... !
YOU COUPLE YOUR PROJECT TO A LIBRARY
!
REACTIVE CODE SPREADS LIKE A VIRUS !
OVERREACTIVE ⚠
DEFINE REACTIVE DESIGN GUIDELINES AND STICK TO
THEM
HAVE REACTIVE FUN !
REFERENCES
> rxmarbles.com> RxSwift Community> RxSwift Repository
> ReactiveCocoa
WE ARE [email protected] - [email protected]
❄ BERLIN - BARCELONA !