reactive cocoa cocoaheadsbe_2014
DESCRIPTION
Presentation on ReactiveCocoa given at the CocoaHeadsBE presentation on March 12th 2014.TRANSCRIPT
ReactiveCocoa
Werner Ramaekers Jan Sabbe
Cocoaheads BE 12/03/2014
ReactiveCocoa (RAC) is an Objective-C framework for
Functional Reactive Programming.
It provides APIs for composing and transforming streams of
values.
Whut ?
Programming
PIN OUT
Take inputs Produce outputs
Imperative programming•int x; int y = 1; int z = 2;
•x = y + z; //x == 3; y == 1; z == 2; !
PIN OUT
Imperative programming•int x; int y = 1; int z = 2;
•x = y + z; //x == 3; y == 1; z == 2; !
PIN OUT
•y = 2;
•NSLog(@“%i”, x); //-> ‘3’ !
# Input changed but output did NOT because it was declared earlier….
Imperative programming•int x; int y = 1; int z = 2;
•x = y + z; //x == 3; y == 1; z == 2; !
PIN OUT
STATE •y = 2;
•NSLog(@“%i”, x); //-> ‘3’ !
# Input changed but output did NOT because it was declared earlier….
Declaractive programming
•The developer describes the problem domain in a declarative way, !
•The focus is NOT on state !
•Focus is on : •data flow
•propagation of change
Functional Programming
•is one way of declarative programming. !
•Functional programming..
•deemphasizes state & mutable data •emphasizes functions that produce
outputs based on inputs : z = f(x, y)
PIN OUT
Reactive Programming
•also is a way of declarative programming !
•Reactive Programming .. •dynamically links the program’s behaviour
to its continuously updating data stream
PIN OUT
Reactive Programming
•also is a way of declarative programming !
•Reactive Programming .. •dynamically links the program’s behaviour
to its continuously updating data stream
PIN OUT
•E.g. : a spreadsheet application where
•cell A1 (= B1 + C1)
Functional Reactive Programming
•.. combines the FUNCTIONAL and REACTIVE programming styles. !
•.. links functions that observe continuous and dynamic streams of data (inputs) to the program’s behaviour (outputs) in real time.
ReactiveCocoa (RAC) is an Objective-C framework for
Functional Reactive Programming.
It provides APIs for composing and transforming streams of
values.
RAC : ReActiveCocoa
Stream Subscriber
RACStream produces a Stream existing of …
RAC : ReActiveCocoa
Stream
Next Event
Subscriber
RACStream produces a Stream existing of …
RAC : ReActiveCocoa
Stream
Next Event
Complete Event
Subscriber
RACStream produces a Stream existing of …
RAC : ReActiveCocoa
Stream
Next Event
Complete Event
Subscriber
Error Event
RACStream produces a Stream existing of …
RAC : ReActiveCocoa
Stream Subscriber
[ 1, 2, 3]
RACSequence
RAC : ReActiveCocoa
Stream Subscriber
[ 1, 2, 3]
123
RACSequence
RAC : ReActiveCocoa
Stream Subscriber
RACSignal
Client Server
RAC : ReActiveCocoa
Stream Subscriber…
JSON
RACSignal
Client Server
RAC : ReActiveCocoa
Stream Subscriber…
∞
RACSignal
Client
listen
RAC : ReActiveCocoa
RACStream
RACSignal RACSequence
ReactiveCocoa •RACStream
•abstract class
•represents some sequential flow of data
•RACSignal (cold)
•push-driven data stream of future values
•cfr Network call
•must be subscribed to in order to access data
•RACSequence
•pull-driven data stream
•similar to Cocoa collection classes (NSArray, ..)
•but values are evaluated lazily - only when needed
Details please
RACSignal!
[self.textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@“New value : %@“, x);
} error:^(NSError *error){
NSLog(@“Error : %@“, error);
} completed:^{
NSLog(@“That’s all folks !”);
}];
RACSignal!
[self.textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@“New value : %@“, x);
!
!
!
!
}];
RACSignal : combineLatest
123
C.L.
( )=,
RACStream operators•Simplifies transformations of streams into
new streams
•map, filter, fold/reduce
!
RACStream : map
MAP =
( ) =
RACSequence *stream = [@[@(1), @(2), @(3)] rac_sequence];
[stream map:^id(id value) {
return @(pow([value integerValue], 2));
}];
NSLog(@“%@“, [stream array]);
RACStream : flatten map
FMAP =
( ) =
Function result = stream
RACStream : filter
RACSequence *stream = [@[@(1), @(2), @(3)]; rac_sequence];
NSLog(@“%@“,
[[[array rac_sequence] filter:^BOOL(id value){
return [value integerValue] % 2 == 0;
}] array]
);
Filter =
( ) = True?
Validating input the “old” way
#pragma mark - UITextFieldDelegate !- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { self.createButton.enabled = [self isFormValid]; ! return YES; }
Validating input the “old” way
#pragma mark - UITextFieldDelegate !- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { self.createButton.enabled = [self isFormValid]; ! return YES; }
- (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]; } … …… …
Validating input the “RAC” wayRAC(self.createButton, enabled) = ! [RACSignal combineLatest:@[ self.usernameTF.rac_textSignal, self.emailFieldTF.rac_textSignal, self.passwordFieldTF.rac_textSignal, self.passwordVerificationFieldTF.rac_textSignal ] reduce:^(NSString *username, NSString *email, NSString *password, NSString *passwordVerification) { return @([username length] > 0 && [email length] > 0 && [password length] > 8 && [password isEqual:passwordVerification]); }];
Validating input the “RAC” wayRAC(self.createButton, enabled) = ! [RACSignal combineLatest:@[ self.usernameTF.rac_textSignal, self.emailFieldTF.rac_textSignal, self.passwordFieldTF.rac_textSignal, self.passwordVerificationFieldTF.rac_textSignal ] reduce:^(NSString *username, NSString *email, NSString *password, NSString *passwordVerification) { return @([username length] > 0 && [email length] > 0 && [password length] > 8 && [password isEqual:passwordVerification]); }];
“not so fast cowboy .. “•RAC(object, key path) is a macro
•creates a one-way binding
•ex. bind “enabled” property of a UIButton to a signal
Now, show me a real app
Station locator app
Station locator app
Station locator app
Station locator app
Core Data
Stations GPS coord Services Prices
Station locator app
Core Data
Stations GPS coord Services Prices
Driving distance
Station locator app
Prices.xml
Core Data
Stations GPS coord Services Prices
Driving distance
Station locator app
Stations.xml Prices.xml
Core Data
Stations GPS coord Services Prices
Driving distance
Easier asynchronous code
Driving distance
1
2
3 Core Data lookup in Background
current location
RAC( ) = (51.2, 5.46)
station (id=4)
flattenMap searchClosest(51.2,5.46)
flattenMap getDrivingDistance(4)
JSON
map JSON -> km
"4.5 km"
Sort stations by driving distance
station (id=1)
station (id=3)
station (id=2)
getDrivingDistance
combineLatest sort [3,1,2]
fetching driving distances concurrentlywhen all available, sort
MVC
MVC
Scott, how do we test UIViewControllers
ViewModel patternViewController sets up bindinganimations
ViewModel (BOOL) shop (BOOL) bread (RACCommand) search
SearchService (RACSignal) searchWithCriteria
validates + delegate to service
do actual search
ViewModel patternViewController sets up bindinganimations
ViewModel (BOOL) shop (BOOL) bread (RACCommand) search
SearchService (RACSignal) searchWithCriteria
mock out the real search service
do search against in memory DB
easily testable
harder to test
hard to test but trivial
ViewModel patternViewController sets up bindinganimations
ViewModel (BOOL) shop (BOOL) bread (RACCommand) search
SearchService (RACSignal) searchWithCriteria
validates + delegate to service
do actual search
what is this?
RACCommand
while getting current location, searching stations, sort by driving distance,... show activity indicator
[[RACCommand alloc] initWithSignalBlock:^RACSignal *(id sender) { return [self createSearchSignal]; }];
ViewModel
RACCommand
while getting current location, searching stations, sort by driving distance,... show activity indicator
[[RACCommand alloc] initWithSignalBlock:^RACSignal *(id sender) { return [self createSearchSignal]; }];
ViewModel
ViewControllerself.searchButton.rac_command = self.viewModel.searchCommand; RAC(self, spinner.hidden) = [self.viewModel.searchCommand.executing not];
ReactiveCocoa …•keeps the app focused on representing data
& UX
•can reduce complexity bc you don’t need to worry about ‘state’ so much
•keeps related code close together (via blocks)
•helps to make your code more testable (via MVVM)
but ReactiveCocoa …•introduces another way of thinking.
•adds another learning curve on top of Cocoa !
•is a framework by GitHub and not Apple !
•will Apple "sherlock" ReactiveCocoa ?
One last thing
Thank you