REACTIVE THINKING IN IOS
DEVELOPMENT@PEPIBUMUR / @SAKY
WHO?
@pepibumur
iOS Developer at SoundCloud
GitHub: pepibumur
Twitter: pepibumur
@saky
iOS Developer at Letgo
GitHub: isaacroldan
Twitter: 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 PROGRAMMING
DECLARATIVE PROGRAMMING
IMPERATIVE PROGRAMMING
DECLARATIVE PROGRAMMING
HOW
SEQUENCE 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 PROGRAMMING
DECLARATIVE PROGRAMMING
WHAT
IT DOESN'T DESCRIBE THE
CONTROL FLOW
HTML
HTML
SQL
HTML
SQL
REACTIVE PROGRAMMING
DECLARATIVE PROGRAMMING
let predicate = NSPredicate(format: "name == %@", "Pedro")
let regex = NSRegularExpression(pattern: ".+", options: 0)
REACTIVE PROGRAMMING
DATA-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
RxSwift
Observable<String>.create { (observer) -> Disposable in
observer.onNext("next value")
observer.onCompleted()
return NopDisposable.instance // For disposing the action
}
ReactiveCocoa
SignalProducer<String>.create { (observer, disposable) in
observer.sendNext("next value")
observer.sendComplete()
}
EXISTING PATTERNSRXSWIFT ▶︎ RXCOCOA (EXTENSIONS)
REACTIVECOCOA ▶︎ DO IT YOURSELF
UIKIT
let button = UIButton()
button.rx_controlEvent(.TouchUpInside)
.subscribeNext { _ in
print("The button was tapped")
}
NOTIFICATIONS
NSNotificationCenter.defaultCenter()
.rx_notification("my_notification", object: nil)
.subscribeNext { notification
// We got a notification
}
DELEGATES
self.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)
})
})
}
}
}
THROTTLING
CLASSIC 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 OBSERVABLEOBSERVABLE ▶ BINDING ▶ OBSERVER
BINDING
To a Variable
let myVariable: Variable<String> = Variable("")
observable
.bindTo(myVariable)
print(myVariable.value)
To UI
observable
.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 }
}
}
UNSUBSCRIPTION
YOU 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 HIRINGPEPI@SOUNDCLOUD.COM - ISAAC@LETGO.COM
❄ BERLIN - BARCELONA !
THANKSQUESTIONS?
SLIDES HTTP://BIT.LY/1RFWLCI
@SAKY - @PEPIBUMUR

Reactive Thinking in iOS Development - Pedro Piñera Buendía - Codemotion Amsterdam 2016