Top Banner
Learn You A ReactiveCocoa For Great Good
63

Learn You a ReactiveCocoa for Great Good

May 11, 2015

Download

Technology

Jason Larsen

Learn why ReactiveCocoa is more than just fancy KVO for Hipsters.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Learn You a ReactiveCocoa for Great Good

Learn You A ReactiveCocoaFor Great Good

Page 2: Learn You a ReactiveCocoa for Great Good

Jason Larsen (@jarsen)Software Engineer at Instructure

Page 3: Learn You a ReactiveCocoa for Great Good

Reactive CocoaMore Than Fancy KVO For Hipsters

Page 4: Learn You a ReactiveCocoa for Great Good

Saying that ReactiveCocoa is just KVO/bindings is like saying that CoreData is just a SQLite

wrapper.

Page 5: Learn You a ReactiveCocoa for Great Good

Functional Programming

• No state

• No side effects

• Immutability

• First class functions

Page 6: Learn You a ReactiveCocoa for Great Good

Reactive Programming

1 3

2 4

3 7 10

Page 7: Learn You a ReactiveCocoa for Great Good

Reactive Programming

1 3

5 4

6 7 13

Page 8: Learn You a ReactiveCocoa for Great Good

FRPFunctional Reactive Programming

Page 9: Learn You a ReactiveCocoa for Great Good

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

Declarativetell the code what to do without telling it how to do it.

Page 10: Learn You a ReactiveCocoa for Great Good

Reduce State(Not eliminate—Obj-C is not purely functional)

Page 11: Learn You a ReactiveCocoa for Great Good

RACStream

RACSequence (pull driven)

RACSignal (push driven)

Page 12: Learn You a ReactiveCocoa for Great Good

A RACStream is like a pipe—new values flow through it. You can subscribe to these values

and then transform them as you please.

Page 13: Learn You a ReactiveCocoa for Great Good

RACSequencePull-Driven Stream

Page 14: Learn You a ReactiveCocoa for Great Good

Sequences

NSArray *numbers = @[ @1, @2, @3 ]; RACSequence *sequence = numbers.rac_sequence;

Page 15: Learn You a ReactiveCocoa for Great Good

Transforming Streams

Page 16: Learn You a ReactiveCocoa for Great Good

Map

RACSequence *numbersSquared = [numbers.rac_sequence map:^id(NSNumber *number) { return @([number intValue] * [number intValue]); }];

Page 17: Learn You a ReactiveCocoa for Great Good

Map

Mapping Function@[@1, @2, @3] @[@1, @4, @9]

Page 18: Learn You a ReactiveCocoa for Great Good

But… for loops!

NSArray *numbers = @[@1,@2,@3]; NSMutableArray *mutableNumbers = [numbers mutableCopy]; for (NSNumber *number in numbers) { [mutableNumbers addObject:@([number intValue] * [number intValue])]; }

Page 19: Learn You a ReactiveCocoa for Great Good

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

Page 20: Learn You a ReactiveCocoa for Great Good

Filter

RACSequence *evenNumbers = [numbers.rac_sequence filter:^BOOL(NSNumber *number) { return @([number intValue] % 2 == 0); }];

Page 21: Learn You a ReactiveCocoa for Great Good

Filter

Filtering Function@[@1, @2, @3] @[@2]

Page 22: Learn You a ReactiveCocoa for Great Good

Fold (a.k.a. reduce)

NSNumber *sum = [numbers.rac_sequence foldLeftWithStart:@0 reduce:^id(id accumulator, id value) { return @([accumulator intValue] + [value intValue]); }];

Page 23: Learn You a ReactiveCocoa for Great Good

Fold

Folding Function@[@1, @2, @3] @6

@0

Page 24: Learn You a ReactiveCocoa for Great Good

Fold LeftSequence

!@[@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

Page 25: Learn You a ReactiveCocoa for Great Good

Combining Streams

Page 26: Learn You a ReactiveCocoa for Great Good

Concatenating

// concatenate letters at end of numbers [numbers concat:letters];

Page 27: Learn You a ReactiveCocoa for Great Good

Flattening Sequences

RACSequence *sequenceOfSequences = @[letters,numbers].rac_sequence; !// flattening sequences concatenates them RACSequence *flattened = [sequenceOfSequences flatten];

Page 28: Learn You a ReactiveCocoa for Great Good

RACSignalPush-Driven Stream

Page 29: Learn You a ReactiveCocoa for Great Good

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]; }];

Page 30: Learn You a ReactiveCocoa for Great Good

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); }];

Page 31: Learn You a ReactiveCocoa for Great Good

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]; }]; }];

Page 32: Learn You a ReactiveCocoa for Great Good

Combining SignalsYo dawg, I heard you like signals…

Page 33: Learn You a ReactiveCocoa for Great Good

Sequencing

// when signal 1 completes, do signal 2 [[signal doNext:^(id x) { NSLog(@"value: %@", x); }] then:^RACSignal *{ return signal2; }];

Page 34: Learn You a ReactiveCocoa for Great Good

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]];

Page 35: Learn You a ReactiveCocoa for Great Good

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]); }];

Page 36: Learn You a ReactiveCocoa for Great Good

Flattening SignalsRACSignal *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];

Page 37: Learn You a ReactiveCocoa for Great Good

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."); }];

Page 38: Learn You a ReactiveCocoa for Great Good

Side EffectsCan’t live with ‘em, can’t live without ‘em

Page 39: Learn You a ReactiveCocoa for Great Good

What is a “side effect?”

• logging

• making a network request

• update the UI

• changing some state somewhere

Page 40: Learn You a ReactiveCocoa for Great Good

Subscriptions

[signal subscribeNext:^(id x) { // do something with each value } error:^(NSError *error) { // do something with errors } completed:^{ // do something with completed }];

Page 41: Learn You a ReactiveCocoa for Great Good

Inject Side Effects[signal doCompleted:^{ // do some side effect after }]; ![signal doNext:^(id x) { // some side effect here }]; ![signal doError:^(NSError *error) { // handle error }];

Page 42: Learn You a ReactiveCocoa for Great Good

Inject Side Effects

[signal initially:^{ // do some side effect before signal }];

Page 43: Learn You a ReactiveCocoa for Great Good

Side Effects

// DO NOT DO [signal map:^id(NSString *string) { NSString *exclamation = [NSString stringWithFormat:@"%@!!!", string]; [self showAlert:exclamation]; return exclamation; }];

Page 44: Learn You a ReactiveCocoa for Great Good

Side Effects

// DO DO [[signal map:^id(NSString *string) { return [NSString stringWithFormat:@"%@!!!", string]; }] doNext:^(NSString *string) { [self showAlert:string]; }];

Page 45: Learn You a ReactiveCocoa for Great Good

RACSubjectManual Signals

Page 46: Learn You a ReactiveCocoa for Great Good

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]; }]; }];

Page 47: Learn You a ReactiveCocoa for Great Good

Concurrency

Page 48: Learn You a ReactiveCocoa for Great Good

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.

Page 49: Learn You a ReactiveCocoa for Great Good

Scenario Solution

// basically: merging signals can replace dispatch groups [[RACSignal merge:@[signal1, signal2, signal3]] then:^RACSignal * { return [self someSignalToDoAfter]; }];

Page 50: Learn You a ReactiveCocoa for Great Good

Replay/Multicasting

Page 51: Learn You a ReactiveCocoa for Great Good

Replace Delegation

• rac_signalForSelector:fromProtocol:

Page 52: Learn You a ReactiveCocoa for Great Good

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.

Page 53: Learn You a ReactiveCocoa for Great Good
Page 54: Learn You a ReactiveCocoa for Great Good

Your hammer 99% of the time

• -subscribeNext:error:completed:

• -doNext: / -doError: / -doCompleted:

• -map:

• -filter:

• -concat:

• -flattenMap:

• -then:

• +merge:

• +combineLatest:reduce:

• -switchToLatest:

Page 55: Learn You a ReactiveCocoa for Great Good

We Want More!

Page 56: Learn You a ReactiveCocoa for Great Good

Specifically, read DesignGuidelines.md and BasicOperators.md

https://github.com/ReactiveCocoa/ReactiveCocoa/tree/master/Documentation

Page 57: Learn You a ReactiveCocoa for Great Good

https://leanpub.com/iosfrp If you’re into books, this one’s decent.

Page 58: Learn You a ReactiveCocoa for Great Good

https://github.com/ReactiveCocoa/ReactiveViewModel

Page 59: Learn You a ReactiveCocoa for Great Good

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

Page 60: Learn You a ReactiveCocoa for Great Good

Don’t Cross the Streams

Page 61: Learn You a ReactiveCocoa for Great Good

Type SafetyA word of warning

Page 62: Learn You a ReactiveCocoa for Great Good

Non KVO Compliancelike textLabel.text

Page 63: Learn You a ReactiveCocoa for Great Good

viewDidLoad Bloatbreak stuff up into setupMethods