gts episode 1: reactive programming in the wild
TRANSCRIPT
Reactive Programming in the wild
Omer Iqbal
Yang Bo
Functional Reactive Programming
Why?
Listeners/Delegates/Observers/Callbacks!
DeathStarDelegate
DeathStar
didBlowUp:
[self fireStormTroopers]
Problems1. Unpredictable Order2. Missing Events3. Cleaning up listeners4. Accidental Recursion5. Messy State6. Multithreading is a PAIN
"our intellectual powers are rather geared to master static relations and that our powers to visualize processes evolving in time are relatively
poorly developed"
Model events as composable values!
Signals/Observables
next next next
completed
next next
error
Creating a Signal- (RACSignal *)login {
return [RACSignal createSignal:^(id<RACSubscriber> subscriber){
[LoginManager loginWithSuccess:^(data){
[subscriber sendNext:data];
[subscriber sendCompleted];
} error:^(NSError *error){
[subscriber sendError:error];
}];
}];
}
Using a signal
[[self login] subscribeNext:^(id response){
} error:^(NSError *error){
} completed:^(){
}];
[vader login:^(id response, NSError *error) { if (error) { return; } [vader blowUpSomePlanets:^(id response, NSError *error) { if (error) { return; } [vader haveBrekkie:^(id response, NSError *error) { if (error) { return; } [vader playWithKids:^(id response, NSError *error) { if (error) { return; } }]; }]; }]; }];
Composition! [[[[vader login] flattenMap:^RACStream *(id value) { return [vader blowUpSomePlanets]; }] flattenMap:^RACStream *(id value) { return [vader haveBrekkie]; }] flattenMap:^RACStream *(id value) { return [vader playWithKids]; }];
flattenMap
merge
combineLatest
Network RequestsRACSignal *buddies = [self requestBuddyList];RACSignal *chats = [self requestChatList];
[RACSignal combineLatest:@[buddies, chats] reduce:^id (NSArray<GxxUser *> *users, NSArray <GxxChat *> *chats){ // .. process here }];
Composable UI FlowsGCActionViewController *vc = [GCActionViewController new];
RACSignal *camera = [[vc addButtonTitle:@"Take Photo"] flattenMap:^(){
return [self presentCameraAndTakeImage];}];
RACSignal *picker = [[vc addButtonTitle:"Choose Existing"] flattenMap:^(){
return [self presentImageSelect];}];
RACSignal *cancel = [[vc didTapCancel] flattenMap:^(){return [RACSignal error:[GCErrorUtility userCancelled]];
}];
return [RACSignal merge:@[camera, picker, cancel]];
iOS
ReactiveCocoa vs RxSwift
● If you’re on objective C, you don’t have a choice -> ReactiveCocoa 2.0
● RxSwift follows standard Rx conventions borrowed from RxJS, RxJava.
● ReactiveCocoa (Swift) has an awesome API with sexy operators which breaks
over Swift versions
● ReactiveCocoa (Swift) distinguishes between hot and cold signals
Problems
Hot Signals
● Easy to miss events● Beware of the phantom subscribe blocks● Always unsubscribe when not needed!
[didUpdateUsers takeUntil:[self rac_willDeallocSignal]]
takeUntil is your best friend
Cold Signals● Only perform work when subscribed to e.g Network Request, UI Flow● Beware of multiple subscribers! ● Use RACMulticast or convert to Hot Signal if multiple subscribers are needed
AndroidYang Bo
“modern apps are event-driven”
System Broadcast○ Internet connectivity
○ Doze mode
○ Download progress
Lifecycle App enters:
○ Foreground
○ Background
Custom Events○ Connection established
○ User logged in
○ Data updated
○ ...
EventBus
Hard truths○ Throttling control
○ Grouping events
○ Higher-order events
○ Error handling and retry
○ Cache and replay
○ ...
R X J A V AANDROID
RxJava to the rescue!
Rx-based Framework Design
Push PullMix
Choices of protocol
Store NodeLatest snapshot of data
Push model
Store NodeIncremental update
Push model
Store NodeInvalidity
Pull model
Request data
Characteristics of “pull” model✓ Throttling-friendly
✓ Controlled memory footprint
✘ Performance cost of requery
Building blocks○ Network manager
public Observable<Response> send(Request request) { ...
}
○ Database manager
public <T> Observable<T> exec(Query<T> query) { ...
}
Framework architecture
UI Data Stream Layer Local Store
ACTION UPDATE
NOTIFY
FETCH
PUSH
Traditional approach
ContactUIData
Name: “44”Icon: “xxxx”
...
Database
User #1
User #2
User #3
User #4
Converter
Task (one-shot)
Data Stream Layer
ContactUIData
Name: “44”Icon: “xxxx”
...
Database
User #1
User #2
User #3
User #4
Data Stream Layer
Data Stream Layer
Database
User #1
User #2
User #3
User #4
Data Stream Layer
Trigger
Filter + Throttle
Provider
Subscriber
NOTIFY
FETCH
Data Stream Layer
Database
User #1
User #2
User #3
User #4
Data Stream Layer
Trigger
Filter + Throttle
Provider
Subscriber
PUSH
#1. Setup once, always up-to-date
#2. Decouple UI from update logic
#3. Unidirectional data flow
Challenges○ Backpressure
Cold observableObservable.range(1, 1000000)
.observeOn(Schedulers.io())
.doOnNext(new Action1<Object>() {
@Override
public void call(Object o) {
// slow ops
}
})
.subscribe();
Hot observableBehaviorSubject.create(new Object())
.observeOn(Schedulers.io())
.doOnNext(new Action1<Object>() {
@Override
public void call(Object o) {
// slow ops
}
})
.subscribe();
No backpressure support!
BehaviorSubject.create(new Object())
.onBackpressureDrop()
.observeOn(Schedulers.io())
.doOnNext(new Action1<Object>() {
@Override
public void call(Object o) {
// slow ops
}
})
.subscribe();
Challenges○ Custom operator?
map concat merge flatMap concatMap switchMap combineLatest from
range create just first firstOrDefault last startWith zip join
switchOnNext amb retry repeat debounce sample defaultIfEmpty
skipUntil skipWhen switchCase takeUntil contains exists isEmpty
distinctUntilChanged share publish refCount replay filter skip
timeout max min reduce count collect toList toMap scan buffer
window cast cache timestamp observeOn subscribeOn delay timer
interval doOnNext doOnComplete doOnError onErrorReturn ...
DON’T BOTHER
Challenges○ Memory leaks
Remember to unsubscribe, always
Subscription subscription = Observable.range(1, 1000000)
.subscribe();
subscription.unsubscribe();
Summary○ Intro to Reactive Programming
○ Practical application in mobile development
○ Pitfalls & Challenges
Thanks