Functional Reactive Programming (CocoaHeads Bratislava)

23
ReactiveCocoa Functional Reactive Programming

Transcript of Functional Reactive Programming (CocoaHeads Bratislava)

ReactiveCocoaFunctional Reactive Programming

Philosophy

!

• Imperative programming

• Functional reactive programming (FRP)

Imperative

• You tell to the computer what to do, how to do it

• Modify program’s states

• Flow control (loops, conditions, methods)

• Manipulation via instances of structs or classes

FRP what is it?• Functional programming

• no states, no side-effects, immutable, first-class objects

• Reactive programming (spreadsheet example)

!

!

• Functional + Reactive = FRP paradigms

A(B+C) B C

2 1 1

FRP frameworks• Objective C, Swift

• ReactiveCocoa (Justin Spahr-Summers, Josh Abernathy)

• Java

• RxJava (Netflix)

• Unity3D

• UniRx (Yoshifumi Kawai)

ReactiveCocoa Building blocks (RACStream)

• RACStream (new value flows)

• Subscriptions (subscribe to receive values)

• Transformations (transform values as you want)

• RACStream

• RACSequence

• RACSignal

RACStream

• Pull-driven stream in pipe (ask for data)

• RACSequence (lazy-loaded collections)

• Push-driven stream in pipe (data in future)

• RACSignal (events: next, complete, error)

RACSequence• Conventional

NSArray *strings = [@"A B C D E F G H I" componentsSeparatedByString:@" “]; NSMutableArray mutableStrings = [NSMutableArray array]; for (NSString* str in strings) {

[mutableStrings addObject:[str stringByAppendingString:str]]; } !

// Contains: AA BB CC DD EE FF GG HH II

• Filter collection

RACSequence• Transform collection

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; RACSequence *mapped = [letters map:^(NSString *value) { return [value stringByAppendingString:value]; }]; !// Contains: AA BB CC DD EE FF GG HH II

RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) { return (value.intValue % 2) == 0; }]; !// Contains: 2 4 6 8

RACSignal• Cold - Lazy, send values only when somebody

subscribed to them, repeated work upon each subscription

• Multicasting - shared signal upon multiple subscriptions

• Hot - rare, immediate work needs to be done

RACSignal

• Conventional- (BOOL)isFormValid { return [self.usernameField.text length] > 0 && [self.emailField.text length] > 0 && [self.passwordField.text length] > 0 && [self.passwordField.text isEqual:self.passwordVerificationField.text]; } !#pragma mark - UITextFieldDelegate !- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { self.createButton.enabled = [self isFormValid]; ! return YES; }

RACSignal

• RAC basedRACSignal *formValid = [RACSignal combineLatest:@[ self.username.rac_textSignal, self.emailField.rac_textSignal, self.passwordField.rac_textSignal, self.passwordVerificationField.rac_textSignal ] reduce:^(NSString *username, NSString *email, NSString *password, NSString *passwordVerification) { return @([username length] > 0 && [email length] > 0 && [password length] > 8 &&

[password isEqual:passwordVerification]); }]; !RAC(self.createButton.enabled) = formValid;

RACSignal Cold

• Signals are cold by default__block int aNumber = 0; !// Signal that will have the side effect of incrementing `aNumber` block // variable for each subscription before sending it. RACSignal *aSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { aNumber++; [subscriber sendNext:@(aNumber)]; [subscriber sendCompleted]; return nil; }]; !// This will print "subscriber one: 1" [aSignal subscribeNext:^(id x) { NSLog(@"subscriber one: %@", x); }]; !// This will print "subscriber two: 2" [aSignal subscribeNext:^(id x) { NSLog(@"subscriber two: %@", x); }];

RACSignal Multicasting

RACSignal *fileSignal = [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) { NSMutableArray *filesInProgress = [NSMutableArray array]; for (NSString *path in files) { [filesInProgress addObject:[NSData dataWithContentsOfFile:path]]; } ! [subscriber sendNext:[filesInProgress copy]]; [subscriber sendCompleted]; }];

• Multiple shared subscriptions

RACSignal Hot

• Multicasted signals are hot, and remain hot until all subscriptions are disposed.RACSignal *dataSignal = [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) {

…. }]; ![dataSignal subscribeNext:^(id x1) { NSLog(@"First %@", x1); }]; ![dataSignal subscribeNext:^(id x2) { NSLog(@"Second %@", x2); }]; !// x1 and x2 values are same, because are those values are shared through hot signal

RACSignal Asynchronous

• Async based operations-(RACSignal *)connect { return [RACSignal createSignal:^(id<RACSubscriber> subscriber) { [externalService connectWithSuccess:^void(id response) {

// Connection succeeded, pass nil (or some useful information) along and complete [subscriber sendNext:response]; [subscriber sendCompleted]; } errorOrTimeout:^void() {

// Error occurred, pass it along to the subscriber [subscriber sendError:someNSError]; }]; }]; }

RACSignal Chaining

[[[[client logIn] then:^{ return [client loadCachedMessages]; }] flattenMap:^(NSArray *messages) { return [client fetchMessagesAfterMessage:messages.lastObject]; }] subscribeError:^(NSError *error) { [self presentError:error]; } completed:^{ NSLog(@"Fetched all messages."); }];

[client logInWithSuccess:^{ [client loadCachedMessagesWithSuccess:^(NSArray *messages) { [client fetchMessagesAfterMessage:messages.lastObject success:^(NSArray *nextMessages) { NSLog(@"Fetched all messages."); } failure:^(NSError *error) { [self presentError:error]; }]; } failure:^(NSError *error) { [self presentError:error]; }]; } failure:^(NSError *error) { [self presentError:error]; }];

• Conventional & and RAC based chained independent operations

Pros• Declarative (code will do it, without telling him

how to do it)

• RAC(self.label, text) = RACObserve(self, name)

• KVO wrapper (less boilerplate code)

• Chain-able

• Smaller code (not in all situations)

Cons

• Sometimes harder to read

• Cannot replace delegate pattern entirely

• UITableView dataSource delegate

• Could have performance hit

• e.g filter (magic behind observing signals)

RAC in Swift• ReactiveCocoa 3.0 (pure in swift with obj-c

bridges)

• Macros not allowed (forget RAC, RACObserve)

• Substitution via custom Structs

• Custom operators

• searchTextField.rac_textSignal() ~> RAC(viewModel, "searchText")

RAC in Swift• Signals with subscription

searchTextField.rac_textSignal().subscribeNext { (next:AnyObject!) -> () in if let text = next as? String { println(countElements(text)) } }

• Signals with subscription reusing swift genericssearchTextField.rac_textSignal().subscribeNextAs { (text:String) -> () in println(countElements(text)) }

Want more?

• https://github.com/ReactiveCocoa/ReactiveCocoa

Thx :) Michal Grman

(Lead senior developer, AIT)