reactivecocoa in practice · why reactivecocoa? “instead of telling a computer how to do it’s...

55
ReactiveCocoa in Practice

Upload: others

Post on 14-Mar-2020

2 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

ReactiveCocoa in Practice

Page 2: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Jeames Bone Mark Corbyn@outware

www.outware.com.au@jeamesbone @markcorbyn

Page 3: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

ReactiveCocoa

Page 4: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

What is ReactiveCocoa?

Functional Reactive Programming (FRP) framework

for iOS and OSX applications.

Page 5: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Why ReactiveCocoa?

“Instead of telling a computer how to do it’s job, why don’t we just tell it

what it’s job is, and let it figure the rest out.”

Page 6: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

ReactiveCocoa in a Nutshell

The main component in ReactiveCocoa is the Signal.

Signals represent streams of values over time.

1 3 2 T

Page 7: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Signals

h he hel T

[textField.rac_textSignal subscribeNext:^(NSString *text) { NSLog(@"text: ‘%@’", text);}];

> text: ‘h’> text: ‘he’> text: ‘hel’

Page 8: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Operators

1 3 2

[signalA map:^(NSNumber *num) {return num * 10;

}];

10 30 20

Page 9: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Operators

1 3 2

[signalA filter:^(NSNumber *num) {return num < 3;

}];

1 2

Page 10: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Operators

a b c

1 3 2

[signalA merge:signalB]

1 3 2a b c

Page 11: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

What’s Next?

?

Page 12: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

1. Reactive View Controller

2. Reactive Notifications

3. Functional Data Processing

Page 13: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Something SimpleAuthentication

/// Stores authentication credentials and tells us if we're authenticated.@protocol AuthStore <NSObject>

@property (nonatomic, readonly) BOOL authenticated;

- (void)storeAccessCredentials:(AccessCredentials *)accessCredentials;- (nullable AccessCredentials *)retrieveAccessCredentials;- (void)removeAccessCredentials;

@end

Page 14: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Observe Authentication Changes

RACSignal *auth = RACObserve(authStore, authenticated);

YES NO YES

Page 15: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Select the Right View Controller

RACSignal *authSignal = RACObserve(authStore, authenticated)

// Pick a view controllerRACSignal *viewControllerSignal = [authenticatedSignal map:^(NSNumber *isAuthenticated) { if (isAuthenticated.boolValue) return [DashboardViewController new]; } else { return [LoginViewController new]; } }];

Page 16: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Select the Right View Controller

YES NO YES

Dashboard Login Dash

board

map { // BOOL to ViewController }

Page 17: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Displaying it on Screen

How do we get the value out?We have to subscribe, like a callback.

- (void)viewDidLoad { [super viewDidLoad];

// Whenever we get a new view controller, PUSH IT [[viewControllerSignal deliverOnMainThread] subscribeNext:^(UIViewController *viewController) { [self showViewController:viewController sender:self]; }];}

Page 18: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Ok, maybe that’s pushing it

Let’s make a custom view controller container!

Page 19: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Switching to the latest view controller

@interface SwitchingController : UIViewController

- (instancetype)initWithViewControllers:(RACSignal *)viewControllers;

@property (nonatomic, readonly) UIViewController *currentViewController;

@end

Manages a signal of view controllers, always displaying the most recent one

Page 20: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

- (void)viewDidLoad { [super viewDidLoad];

[[self.viewControllers deliverOnMainThread] subscribeNext:^(UIViewController *viewController) { [self transitionFrom:self.currentViewController to:viewController]; }];}

Setting Up

When the view loads, subscribe to our signal

Page 21: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

- (void)transitionFrom:(UIViewController *)fromViewController to:(UIViewController *)toViewController { if (!fromViewController) { [self addInitialViewController:toViewController]; return; }

[self addChildViewController:nextViewController]; nextViewController.view.frame = self.view.bounds; [self.view addSubview:nextViewController.view]; [previousViewController willMoveToParentViewController:nil];

[self transitionFromViewController:fromViewController toViewController:toViewController duration:0.5 options:UIViewAnimationOptionTransitionCrossDissolve animations:nil completion:^(BOOL finished) { [toViewController didMoveToParentViewController:self]; [fromViewController removeFromParentViewController]; self.currentViewController = toViewController; }];}

Transitioning

Page 22: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

YES NO YES

DashboardLogin

Page 23: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Finishing Up

What happens if the view controller changes rapidly?

Page 24: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

- (void)viewDidLoad { [super viewDidLoad];

[[[self.viewControllers throttle:0.5] deliverOnMainThread] subscribeNext:^(UIViewController *viewController) { [self transitionFrom:self.currentViewController to:viewController]; }];}

Simple Throttling

Only take a new view controller if 0.5 seconds have passed.

Page 25: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

v2: Only throttle while animating

Keep track of when we’re animating

- (void)transitionFrom:(UIViewController *)fromViewController to:(UIViewController *)toViewController { // code self.animating = YES;

[self transitionFromViewController:fromViewController toViewController:toViewController duration:0.5 options:UIViewAnimationOptionTransitionCrossDissolve animations:nil completion:^(BOOL finished) { // more code self.animating = NO; }];}

Page 26: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

v2: Only throttle while animating

Throttle only if we are animating

[[[self.viewControllers throttle:0.5 valuesPassingTest:^(id _) { return self.isAnimating; }] deliverOnMainThread] subscribeNext:^(UIViewController *viewController) { [self transitionFrom:self.currentViewController to:viewController]; }];

Page 27: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

What have we learned?

• Signals can represent real world events (Logging in/out)

• We can transform them using operators like map

• We can control timing through operators like throttle

Page 28: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

1. Reactive View Controller

2. Reactive Notifications

3. Functional Data Processing

Page 29: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Hot Signals

• Events happen regardless of any observers.

• Stream of *events* happening in the world.

• e.g. UI interaction, notifications

Page 30: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Cold Signals

• Subscribing starts the stream of events.

• Stream of results caused by some side effects.

• e.g. network calls, database transactions

Page 31: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Push Notifications

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { // Do something horrible™ in here}

Page 32: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

A Better Option

typedef NS_ENUM(NSUInteger, NotificationType) { NotificationTypeA, NotificationTypeB, NotificationTypeC,};

@protocol NotificationProvider

- (RACSignal *)notificationSignalForNotificationType: (NotificationType)type;

@end

Page 33: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

We Want This:

@property id<NotificationProvider> notificationProvider;

- (void)viewDidLoad { [[[self.notificationProvider notificationSignalForNotificationType:NotificationTypeA] deliverOnMainThread] subscribeNext:^(Notification *notification) { [self updateInterfaceWithNotification:notification]; }] }

Page 34: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

A New Friend!

Lift a selector into the reactive world

- (RACSignal *)rac_signalForSelector:(SEL)selector;

The returned signal will fire an event every time the method is called.

Page 35: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Let’s Do It!

- (RACSignal *)notificationSignalForNotificationType: (NotificationType)type { return [[[self rac_signalForSelector:@selector(application:didReceiveRemoteNotification:)] map:^(RACTuple *arguments) { return arguments.second; }] map:^(NSDictionary *userInfo) { // Parse our user info dictionary into a model object return [self parseNotification:userInfo]; }] filter:^(Notification *notification) notification.type = type; }];}

Page 36: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

NSDict NSDict NSDict

NotificationA

NotificationB

NotificationA

map { [self parseNotification:userInfo]; }

filter { notification.type == typeA }

NotificationA

NotificationA

Page 37: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

A Wild Local Notification Appears!

• We don't want to duplicate our current notification handling.

• Local and remote notifications should have the same effects.

Page 38: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

RACSignal *remoteNotificationInfo = [[self rac_signalForSelector:@selector(application:didReceiveRemoteNotification:)] map:^(RACTuple *arguments) { return arguments.second }];

RACSignal *localNotificationInfo = [[self rac_signalForSelector:@selector(application:didReceiveLocalNotification:)] map:^(RACTuple *arguments) { UILocalNotification *notification = arguments.second; return notification.userInfo; }];

return [[[remoteNotificationInfo merge:localNotificationInfo] map:^(NSDictionary *userInfo) { // Parse our user info dictionary into a model object return [self parseNotification:userInfo]; }]

filter:^(Notification *notification) { return notification.type == type;}];

Page 39: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Problem

• We only get notifications sent *after* we subscribe.

• We can't easily update app state or UI that is created afterthe notification is sent.

Page 40: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Solution?

[signal replayLast]

Whenever you subscribe to the signal, it will immediately send you the most recent value from the stream.

Page 41: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Replay it

return [[[remoteNotificationInfo merge:localNotificationInfo] map:^(NSDictionary *userInfo) { // Parse our user info dictionary into a model object return [self parseNotification:userInfo]; }] filter:^(Notification *notification) { return notification.type == type; }] replayLast];

return [[remoteNotificationInfo merge:localNotificationInfo] map:^(NSDictionary *userInfo) { // Parse our user info dictionary into a model object return [self parseNotification:userInfo]; }] filter:^(Notification *notification) { return notification.type == type; }];

Page 42: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

What have we learned?

• Signals can model complex app behaviour like notifications

• We can combine signals in interesting ways

• Helpers like signalForSelector allow us to lift regular functions into signals

Page 43: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

1. Reactive View Controller

2. Reactive Notifications

3. Functional Data Processing

Page 44: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Describing an Algorithm with FunctionsExample: Finding the best voucher to cover a

purchase

• If there are any vouchers with higher value than the purchase, use the lowest valued of those

• Otherwise, use the highest valued voucher available

Page 45: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Imperative Approach

Page 46: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

NSArray *sortedVouchers = [vouchers sortedArrayUsingComparator:^NSComparisonResult(id<Voucher> voucher1, id<Voucher> voucher2) { return [voucher1 compare:voucher2];}];

NSArray *vouchers = [[self voucherLibrary] vouchers];

id<Voucher> bestVoucher = nil; for (id<Voucher> voucher in sortedVouchers) { NSDecimalNumber *voucherAmount = voucher.amount; if (!voucherAmount) continue;

if ([voucherAmount isLessThan:purchaseAmount]) { bestVoucher = voucher; } else if ([voucherAmount isGreaterThanOrEqualTo:purchaseAmount]) { bestVoucher = voucher; break; } }

return bestVoucher;}

Page 47: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

NSMutableArray *vouchersWithValue = [NSMutableArray array];for (id<Voucher> voucher in sortedVouchers) { if (voucher.amount) { [vouchersWithValue addObject:voucher]; }}

id<Voucher> bestVoucher = nil;for (id<Voucher> voucher in vouchersWithValue) { NSDecimalNumber *voucherAmount = voucher.amount; if ([voucherAmount isLessThen:purchaseAmount]) { bestVoucher = voucher; } else if ([voucherAmount isGreaterThanOrEqualTo:purchaseAmount]) { bestVoucher = voucher; break; }}

Separate the Filter?

Page 48: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Functional Approach

Page 49: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

- (id<Voucher>)voucherForPurchaseAmount:(NSDecimalNumber *)purchaseAmount { [[[[[[[[self voucherLibrary] vouchers] sortedArrayUsingComparator:^NSComparisonResult(id<Voucher> voucher1, id<Voucher> voucher2) { return [voucher1 compare:voucher2]; }] rac_sequence] filter:^BOOL(id<Voucher> voucher) { return voucher.amount != nil; }] yow_takeUptoBlock:^BOOL(id<Voucher> voucher) { return [voucher.amount isGreaterThanOrEqualTo:purchaseAmount]; }] array] lastObject];}

Page 50: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

NSArray *sortedVouchers = [vouchers sortedArrayUsingComparator:^NSComparisonResult(id<Voucher> voucher1, id<Voucher> voucher2) { return [voucher1 compare:voucher2];}];

NSArray *vouchers = [[self voucherLibrary] vouchers];

id<Voucher> bestVoucher = nil; for (id<Voucher> voucher in sortedVouchers) { NSDecimalNumber *voucherAmount = voucher.amount; if (!voucherAmount) continue;

if ([voucherAmount isLessThan:purchaseAmount]) { bestVoucher = voucher; } else if ([voucherAmount isGreaterThanOrEqualTo:purchaseAmount]) { bestVoucher = voucher; break; } }

return bestVoucher;}

Page 51: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

What have we learned?

• Functional code can be more readable

• It's easy to convert between imperative and functional code using ReactiveCocoa

• Signals are lazy

Page 52: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

1. Reactive View Controller

2. Reactive Notifications

3. Functional Data Processing

Page 53: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Resources

• reactivecocoa.io

• ReactiveCocoa on GitHub

• ReactiveCocoa - The Definitive Introduction (Ray Wenderlich)

• A First Look at ReactiveCocoa 3.0 (Scott Logic)

Page 54: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Next Steps

• Try it out!

• Don’t be scared to go all in

Page 55: ReactiveCocoa in Practice · Why ReactiveCocoa? “Instead of telling a computer how to do it’s job, why don’t we just tell it what it’s job is, and let it figure the rest out.”

Thanks!Questions?