GTS Episode 1: Reactive programming in the wild

Post on 09-Jan-2017

160 views 3 download

Transcript of GTS Episode 1: Reactive programming in the wild

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