Presented at YOW! Connected 2015 (Melbourne) by Jeames Bone & Mark Corbyn:
"There are many great resources for getting started with Functional Reactive Programming and ReactiveCocoa, but whats the next step? ReactiveCocoa is not just a nice wrapper for KVO, Signals can be used to model many common problems in Cocoa including managing the state of your UI, notifications and even business logic. Adopting ReactiveCocoa can make for more modular, self-documenting code while still integrating easily with other APIs and your existing code. We would like to share with you some interesting, practical examples where weve used ReactiveCocoa to solve problems in our app. Our goal is to inspire you to consider how ReactiveCocoa can be applied in your own apps."
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];
}
}];
16. Select the Right View Controller
YES NO YES
Dash
board
Login
Dash
board
map { // BOOL to ViewController }
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];
}];
}
18. Ok, maybe thats pushing it
Lets make a custom view controller
container!
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
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
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.
25. v2: Only throttle while animating
Keep track of when were 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;
}];
}
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];
}];
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
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.
35. Lets 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;
}];
}
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.
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.
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;
}];
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
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
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