ReactiveCocoa Functional Reactive Programming
Jul 18, 2015
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