Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Learn You A ReactiveCocoa
For Great Good
Jason Larsen (@jarsen)
Software Engineer at Instructure
Reactive Cocoa
More Than Fancy KVO For Hipsters
Saying that ReactiveCocoa is just KVO/bindings
is like saying that CoreData is just a SQLite
wrapper.
Functional Programming
• No state
• No side effects
• Immutability
• First class functions
Reactive Programming
1 3
2 4
3 7 10
Reactive Programming
1 3
5 4
6 7 13
FRP
Functional Reactive Programming
RAC(self, label.text) = RACObserve(self, name);
Declarative
tell the code what to do without telling it how to do it.
Reduce State
(Not eliminate—Obj-C is not purely functional)
RACStream
RACSequence
(pull driven)
RACSignal
(push driven)
A RACStream is like a pipe—new values flow
through it. You can subscribe to these values
and then transform them as you ple...
RACSequence
Pull-Driven Stream
Sequences
NSArray *numbers = @[ @1, @2, @3 ];
RACSequence *sequence = numbers.rac_sequence;
Transforming Streams
Map
RACSequence *numbersSquared = [numbers.rac_sequence
map:^id(NSNumber *number) {
return @([number intValue] * [number i...
Map
Mapping
Function
@[@1, @2, @3] @[@1, @4, @9]
But… for loops!
NSArray *numbers = @[@1,@2,@3];
NSMutableArray *mutableNumbers = [numbers mutableCopy];
for (NSNumber *num...
Lazy Evaluation
NSArray *strings = @[ @"A", @"B", @"C" ];
RACSequence *sequence = [strings.rac_sequence
map:^(NSString *st...
Filter
RACSequence *evenNumbers = [numbers.rac_sequence
filter:^BOOL(NSNumber *number) {
return @([number intValue] % 2 ==...
Filter
Filtering
Function
@[@1, @2, @3] @[@2]
Fold (a.k.a. reduce)
NSNumber *sum = [numbers.rac_sequence foldLeftWithStart:@0
reduce:^id(id accumulator, id value) {
ret...
Fold
Folding
Function
@[@1, @2, @3] @6
@0
Fold Left
Sequence
!
@[@1, @2, @3, @4]
@[@1, @2, @3, @4]
@[@1, @2, @3, @4]
@[@1, @2, @3, @4]
@[@1, @2, @3, @4]
Accumulator...
Combining Streams
Concatenating
// concatenate letters at end of numbers
[numbers concat:letters];
Flattening Sequences
RACSequence *sequenceOfSequences =
@[letters,numbers].rac_sequence;
!
// flattening sequences concate...
RACSignal
Push-Driven Stream
Map
// set the label to always be equal to the formatted
// string of “12 units”, where 12 is whatever the
// current valu...
Filter
// only set total to the even numbers in
// the stream
RAC(self, total) = [RACSignal(self, cell1)
filter:^BOOL(NSNu...
Creating Signals
[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber)
{
NSURLSessionDataTask *task = [se...
Combining Signals
Yo dawg, I heard you like signals…
Sequencing
// when signal 1 completes, do signal 2
[[signal doNext:^(id x) {
NSLog(@"value: %@", x);
}]
then:^RACSignal *{...
Merging Signals
// creates a new signal that will send the
// values of both signals, complete when both
// are completed,...
Combine Latest Values
RACSignal *cell1Signal = RACObserve(self, cell1);
RACSignal *cell2Signal = RACObserve(self, cell2);
...
Flattening Signals
RACSignal *signalOfSignals = [RACSignal
createSignal:^ RACDisposable *
(id<RACSubscriber> subscriber) {...
Flattening & Mapping
// creates multiple signals of work which
// are automatically recombined, or in other words
// it ma...
Side Effects
Can’t live with ‘em, can’t live without ‘em
What is a “side effect?”
• logging
• making a network request
• update the UI
• changing some state somewhere
Subscriptions
[signal subscribeNext:^(id x) {
// do something with each value
} error:^(NSError *error) {
// do something ...
Inject Side Effects
[signal doCompleted:^{
// do some side effect after
}];
!
[signal doNext:^(id x) {
// some side effect...
Inject Side Effects
[signal initially:^{
// do some side effect before signal
}];
Side Effects
// DO NOT DO
[signal map:^id(NSString *string) {
NSString *exclamation = [NSString
stringWithFormat:@"%@!!!",...
Side Effects
// DO DO
[[signal map:^id(NSString *string) {
return [NSString
stringWithFormat:@"%@!!!", string];
}] doNext:...
RACSubject
Manual Signals
Use createSignal: over
RACSubject when possible
[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber)
{
N...
Concurrency
Scenario
• I want to run three networking calls and then when
they are all done do something
• I want to MERGE three signa...
Scenario Solution
// basically: merging signals can replace dispatch groups
[[RACSignal merge:@[signal1, signal2, signal3]...
Replay/Multicasting
Replace Delegation
• rac_signalForSelector:fromProtocol:
Other Cool Methods
• -throttle:
• -takeUntil: can be used to automatically dispose of a
subscription when an event occurs ...
Your hammer
99% of the time
• -subscribeNext:error:completed:
• -doNext: / -doError: / -doCompleted:
• -map:
• -filter:
• -...
We Want More!
Specifically, read DesignGuidelines.md
and BasicOperators.md
https://github.com/ReactiveCocoa/
ReactiveCocoa/tree/master/Do...
https://leanpub.com/iosfrp
If you’re into books, this one’s decent.
https://github.com/ReactiveCocoa/
ReactiveViewModel
Github’s Obj-C API Lib
https://github.com/octokit/octokit.objc
!
(and Instructure’s API Lib modeled after it)
https://gith...
Don’t Cross the Streams
Type Safety
A word of warning
Non KVO Compliance
like textLabel.text
viewDidLoad Bloat
break stuff up into setupMethods
Learn You a ReactiveCocoa for Great Good
Upcoming SlideShare
Loading in …5
×

Learn You a ReactiveCocoa for Great Good

5,225 views

Published on

Learn why ReactiveCocoa is more than just fancy KVO for Hipsters.

Published in: Technology

Learn You a ReactiveCocoa for Great Good

  1. 1. Learn You A ReactiveCocoa For Great Good
  2. 2. Jason Larsen (@jarsen) Software Engineer at Instructure
  3. 3. Reactive Cocoa More Than Fancy KVO For Hipsters
  4. 4. Saying that ReactiveCocoa is just KVO/bindings is like saying that CoreData is just a SQLite wrapper.
  5. 5. Functional Programming • No state • No side effects • Immutability • First class functions
  6. 6. Reactive Programming 1 3 2 4 3 7 10
  7. 7. Reactive Programming 1 3 5 4 6 7 13
  8. 8. FRP Functional Reactive Programming
  9. 9. RAC(self, label.text) = RACObserve(self, name); Declarative tell the code what to do without telling it how to do it.
  10. 10. Reduce State (Not eliminate—Obj-C is not purely functional)
  11. 11. RACStream RACSequence (pull driven) RACSignal (push driven)
  12. 12. A RACStream is like a pipe—new values flow through it. You can subscribe to these values and then transform them as you please.
  13. 13. RACSequence Pull-Driven Stream
  14. 14. Sequences NSArray *numbers = @[ @1, @2, @3 ]; RACSequence *sequence = numbers.rac_sequence;
  15. 15. Transforming Streams
  16. 16. Map RACSequence *numbersSquared = [numbers.rac_sequence map:^id(NSNumber *number) { return @([number intValue] * [number intValue]); }];
  17. 17. Map Mapping Function @[@1, @2, @3] @[@1, @4, @9]
  18. 18. But… for loops! NSArray *numbers = @[@1,@2,@3]; NSMutableArray *mutableNumbers = [numbers mutableCopy]; for (NSNumber *number in numbers) { [mutableNumbers addObject:@([number intValue] * [number intValue])]; }
  19. 19. Lazy Evaluation NSArray *strings = @[ @"A", @"B", @"C" ]; RACSequence *sequence = [strings.rac_sequence map:^(NSString *str) { return [str stringByAppendingString:@"_"]; }]; ! sequence.head; // evaluates @"A_" sequence.tail.head; // evaluates @"B_" sequence.eagerSequence; // evaluates sequence
  20. 20. Filter RACSequence *evenNumbers = [numbers.rac_sequence filter:^BOOL(NSNumber *number) { return @([number intValue] % 2 == 0); }];
  21. 21. Filter Filtering Function @[@1, @2, @3] @[@2]
  22. 22. Fold (a.k.a. reduce) NSNumber *sum = [numbers.rac_sequence foldLeftWithStart:@0 reduce:^id(id accumulator, id value) { return @([accumulator intValue] + [value intValue]); }];
  23. 23. Fold Folding Function @[@1, @2, @3] @6 @0
  24. 24. Fold Left Sequence ! @[@1, @2, @3, @4] @[@1, @2, @3, @4] @[@1, @2, @3, @4] @[@1, @2, @3, @4] @[@1, @2, @3, @4] Accumulator ! @0 @1 @3 @6 @10
  25. 25. Combining Streams
  26. 26. Concatenating // concatenate letters at end of numbers [numbers concat:letters];
  27. 27. Flattening Sequences RACSequence *sequenceOfSequences = @[letters,numbers].rac_sequence; ! // flattening sequences concatenates them RACSequence *flattened = [sequenceOfSequences flatten];
  28. 28. RACSignal Push-Driven Stream
  29. 29. Map // set the label to always be equal to the formatted // string of “12 units”, where 12 is whatever the // current value of self.total RACSignal *signal = RACObserve(self, total); RAC(self, totalLabel.text) = [signal map:^id(NSNumber *total) { [NSString stringWithFormat:@"%i units", total]; }];
  30. 30. Filter // only set total to the even numbers in // the stream RAC(self, total) = [RACSignal(self, cell1) filter:^BOOL(NSNumber *number) { return @([number intValue] % 2 == 0); }];
  31. 31. Creating Signals [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSURLSessionDataTask *task = [self PUT:path parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) { [subscriber sendNext:responseObject]; } failure:^(NSURLSessionDataTask *task, NSError *error) { [subscriber sendError:error]; }]; return [RACDisposable disposableWithBlock:^{ [task cancel]; }]; }];
  32. 32. Combining Signals Yo dawg, I heard you like signals…
  33. 33. Sequencing // when signal 1 completes, do signal 2 [[signal doNext:^(id x) { NSLog(@"value: %@", x); }] then:^RACSignal *{ return signal2; }];
  34. 34. Merging Signals // creates a new signal that will send the // values of both signals, complete when both // are completed, and error when either errors [RACSignal merge:@[signal1, signal2]];
  35. 35. Combine Latest Values RACSignal *cell1Signal = RACObserve(self, cell1); RACSignal *cell2Signal = RACObserve(self, cell2); RAC(self, total) = [RACSignal combineLatest:@[cell1Signal, cell2Signal] reduce:^id(id cell1, id cell2){ return @([cell1 intValue] + [cell2 intValue]); }];
  36. 36. Flattening Signals RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { [subscriber sendNext:letters]; [subscriber sendNext:numbers]; [subscriber sendCompleted]; return nil; }]; ! // flattening signals merges them RACSignal *flattened = [signalOfSignals flatten];
  37. 37. Flattening & Mapping // creates multiple signals of work which // are automatically recombined, or in other words // it maps each letter to a signal using // saveEntriesForLetter: and then it merges them all. letters = @[@“a”, @“b”, @“c”] [[letters flattenMap:^(NSString *letter) { return [database saveEntriesForLetter:letter]; }] subscribeCompleted:^{ NSLog(@"All database entries saved successfully."); }];
  38. 38. Side Effects Can’t live with ‘em, can’t live without ‘em
  39. 39. What is a “side effect?” • logging • making a network request • update the UI • changing some state somewhere
  40. 40. Subscriptions [signal subscribeNext:^(id x) { // do something with each value } error:^(NSError *error) { // do something with errors } completed:^{ // do something with completed }];
  41. 41. Inject Side Effects [signal doCompleted:^{ // do some side effect after }]; ! [signal doNext:^(id x) { // some side effect here }]; ! [signal doError:^(NSError *error) { // handle error }];
  42. 42. Inject Side Effects [signal initially:^{ // do some side effect before signal }];
  43. 43. Side Effects // DO NOT DO [signal map:^id(NSString *string) { NSString *exclamation = [NSString stringWithFormat:@"%@!!!", string]; [self showAlert:exclamation]; return exclamation; }];
  44. 44. Side Effects // DO DO [[signal map:^id(NSString *string) { return [NSString stringWithFormat:@"%@!!!", string]; }] doNext:^(NSString *string) { [self showAlert:string]; }];
  45. 45. RACSubject Manual Signals
  46. 46. Use createSignal: over RACSubject when possible [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSURLSessionDataTask *task = [client PUT:path parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) { [subscriber sendNext:responseObject]; } failure:^(NSURLSessionDataTask *task, NSError *error) { [subscriber sendError:error]; }]; return [RACDisposable disposableWithBlock:^{ [task cancel]; }]; }];
  47. 47. Concurrency
  48. 48. Scenario • I want to run three networking calls and then when they are all done do something • I want to MERGE three signals and THEN do something.
  49. 49. Scenario Solution // basically: merging signals can replace dispatch groups [[RACSignal merge:@[signal1, signal2, signal3]] then:^RACSignal * { return [self someSignalToDoAfter]; }];
  50. 50. Replay/Multicasting
  51. 51. Replace Delegation • rac_signalForSelector:fromProtocol:
  52. 52. Other Cool Methods • -throttle: • -takeUntil: can be used to automatically dispose of a subscription when an event occurs (like a "Cancel" button being pressed in the UI). • -setNameWithFormat: for debugging • -logNext, -logError, -logCompleted, -logAll automatically log signal events as they occur, and include the name of the signal in the messages. This can be used to conveniently inspect a signal in real-time.
  53. 53. Your hammer 99% of the time • -subscribeNext:error:completed: • -doNext: / -doError: / -doCompleted: • -map: • -filter: • -concat: • -flattenMap: • -then: • +merge: • +combineLatest:reduce: • -switchToLatest:
  54. 54. We Want More!
  55. 55. Specifically, read DesignGuidelines.md and BasicOperators.md https://github.com/ReactiveCocoa/ ReactiveCocoa/tree/master/Documentation
  56. 56. https://leanpub.com/iosfrp If you’re into books, this one’s decent.
  57. 57. https://github.com/ReactiveCocoa/ ReactiveViewModel
  58. 58. Github’s Obj-C API Lib https://github.com/octokit/octokit.objc ! (and Instructure’s API Lib modeled after it) https://github.com/instructure/canvaskit
  59. 59. Don’t Cross the Streams
  60. 60. Type Safety A word of warning
  61. 61. Non KVO Compliance like textLabel.text
  62. 62. viewDidLoad Bloat break stuff up into setupMethods

×