ReactiveCocoa in Practice Jeames Bone Mark Corbyn @jeamesbone - - PowerPoint PPT Presentation

reactivecocoa in practice
SMART_READER_LITE
LIVE PREVIEW

ReactiveCocoa in Practice Jeames Bone Mark Corbyn @jeamesbone - - PowerPoint PPT Presentation

ReactiveCocoa in Practice Jeames Bone Mark Corbyn @jeamesbone @outware @markcorbyn www.outware.com.au ReactiveCocoa What is ReactiveCocoa? Functional Reactive Programming (FRP) framework for iOS and OSX applications. Why ReactiveCocoa?


slide-1
SLIDE 1

ReactiveCocoa in Practice

slide-2
SLIDE 2

Jeames Bone

Mark Corbyn

@outware www.outware.com.au @jeamesbone @markcorbyn

slide-3
SLIDE 3

ReactiveCocoa

slide-4
SLIDE 4

What is ReactiveCocoa?

Functional Reactive Programming (FRP) framework for iOS and OSX applications.

slide-5
SLIDE 5

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.”

slide-6
SLIDE 6

ReactiveCocoa in a Nutshell

The main component in ReactiveCocoa is the Signal. Signals represent streams of values over time.

1 3 2

T

slide-7
SLIDE 7

Signals

h he hel

T

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

slide-8
SLIDE 8

Operators

1 3 2

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

10 30 20

slide-9
SLIDE 9

Operators

1 3 2

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

1 2

slide-10
SLIDE 10

Operators

a b c 1 3 2

[signalA merge:signalB]

1 3 2 a b c

slide-11
SLIDE 11

What’s Next?

?

slide-12
SLIDE 12
  • 1. Reactive View Controller
  • 2. Reactive Notifications
  • 3. Functional Data

Processing

slide-13
SLIDE 13

Something Simple

Authentication

/// 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

slide-14
SLIDE 14

Observe Authentication Changes

RACSignal *auth = RACObserve(authStore, authenticated);

YES NO YES

slide-15
SLIDE 15

Select the Right View Controller

RACSignal *authSignal = RACObserve(authStore, authenticated) // Pick a view controller RACSignal *viewControllerSignal = [authenticatedSignal map:^(NSNumber *isAuthenticated) { if (isAuthenticated.boolValue) return [DashboardViewController new]; } else { return [LoginViewController new]; } }];

slide-16
SLIDE 16

Select the Right View Controller

YES NO YES

Dash board Login Dash board

map { // BOOL to ViewController }

slide-17
SLIDE 17

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

slide-18
SLIDE 18

Ok, maybe that’s pushing it

Let’s make a custom view controller container!

slide-19
SLIDE 19

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

slide-20
SLIDE 20
  • (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

slide-21
SLIDE 21
  • (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

  • ptions:UIViewAnimationOptionTransitionCrossDissolve

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

Transitioning

slide-22
SLIDE 22

YES NO YES

Dashboard Login

slide-23
SLIDE 23

Finishing Up

What happens if the view controller changes rapidly?

slide-24
SLIDE 24
  • (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.

slide-25
SLIDE 25

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

  • ptions:UIViewAnimationOptionTransitionCrossDissolve

animations:nil completion:^(BOOL finished) { // more code self.animating = NO; }]; }

slide-26
SLIDE 26

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

slide-27
SLIDE 27

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
slide-28
SLIDE 28
  • 1. Reactive View Controller
  • 2. Reactive Notifications
  • 3. Functional Data

Processing

slide-29
SLIDE 29

Hot Signals

  • Events happen regardless of any observers.
  • Stream of *events* happening in the world.
  • e.g. UI interaction, notifications
slide-30
SLIDE 30

Cold Signals

  • Subscribing starts the stream of events.
  • Stream of results caused by some side effects.
  • e.g. network calls, database transactions
slide-31
SLIDE 31

Push Notifications

  • (void)application:(UIApplication *)application

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

slide-32
SLIDE 32

A Better Option

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

  • (RACSignal *)notificationSignalForNotificationType:

(NotificationType)type; @end

slide-33
SLIDE 33

We Want This:

@property id<NotificationProvider> notificationProvider;

  • (void)viewDidLoad {

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

slide-34
SLIDE 34

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.

slide-35
SLIDE 35

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

slide-36
SLIDE 36

NSDict NSDict NSDict

Notificati

  • n

A Notificati

  • n

B Notificati

  • n

A

map { [self parseNotification:userInfo]; } filter { notification.type == typeA }

Notificati

  • n

A Notificati

  • n

A

slide-37
SLIDE 37

A Wild Local Notification Appears!

  • We don't want to duplicate our current notification handling.
  • Local and remote notifications should have the same effects.
slide-38
SLIDE 38

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

slide-39
SLIDE 39

Problem

  • We only get notifications sent *after* we subscribe.
  • We can't easily update app state or UI that is created after


the notification is sent.

slide-40
SLIDE 40

Solution?

[signal replayLast]

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

slide-41
SLIDE 41

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

slide-42
SLIDE 42

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
slide-43
SLIDE 43
  • 1. Reactive View Controller
  • 2. Reactive Notifications
  • 3. Functional Data

Processing

slide-44
SLIDE 44

Describing an Algorithm with Functions

Example: 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
slide-45
SLIDE 45

Imperative Approach

slide-46
SLIDE 46

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

slide-47
SLIDE 47

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?

slide-48
SLIDE 48

Functional Approach

slide-49
SLIDE 49
  • (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]; }

slide-50
SLIDE 50

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

slide-51
SLIDE 51

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
slide-52
SLIDE 52
  • 1. Reactive View Controller
  • 2. Reactive Notifications
  • 3. Functional Data

Processing

slide-53
SLIDE 53

Resources

  • reactivecocoa.io
  • ReactiveCocoa on GitHub
  • ReactiveCocoa - The Definitive Introduction (Ray Wenderlich)
  • A First Look at ReactiveCocoa 3.0 (Scott Logic)
slide-54
SLIDE 54

Next Steps

  • Try it out!
  • Don’t be scared to go all in
slide-55
SLIDE 55

Thanks!

Questions?