reactivecocoa - impathic bindings vs. reactivecocoa

33
ReactiveCocoa Marc Prud'hommeaux < [email protected] > Ottawa CocoaHeads February 13th, 2014

Upload: trinhhanh

Post on 22-Apr-2018

257 views

Category:

Documents


12 download

TRANSCRIPT

Page 1: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

ReactiveCocoa

Marc Prud'hommeaux <[email protected]> Ottawa CocoaHeads February 13th, 2014

Page 2: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Introduction

Page 3: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

ReactiveCocoa

• Functional Reactive Programming

• Open-source library by Josh Abernathy & Justin Spahr-Summers of GitHub

• Modeled on Microsoft's Reactive Extensions for .NET

Page 4: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Features

• The ability to compose operations on future data.

• An approach to minimize state and mutability.

• A declarative way to define behaviors and the relationships between properties.

• A unified, high-level interface for asynchronous operations.

• An API on top of KVO

Page 5: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

State Changes are Signals

• Anyone can "subscribe" to a "signal"

• A signal can be:

• Tapping a UIButton

• Receiving data on a NSURLConnection

• A global event in NSNotificationCenter

• ... and any other future state change

Page 6: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Less Spaghetti!

Features (summary)

Page 7: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Functional Reactive Programming

• First suggested in ICFP 97 paper Functional Reactive Animation by Conal Elliott and Paul Hudak.

Page 8: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Haskell 101

Page 9: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Coding, Observing, & Binding

Page 10: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Background: Key-Value Coding

Key-Value Coding (KVC) "itemPrice" ⇄ productModel.itemPrice

Page 11: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

@interface Product : NSObject @property (strong) NSString *itemTitle; @property double itemPrice; @end !@implementation Product @end !Product *product = [[Product alloc] init]; ![product setItemPrice:10.0]; NSAssert([product itemPrice] == 10.0, @"price via setter"); !product.itemPrice += 5.0; NSAssert(product.itemPrice == 15.0, @"price via property"); ![product setValue:@25.0 forKey:@"itemPrice"]; NSAssert([[product valueForKey:@"itemPrice"] isEqual:@25.0], @"KVC");

Background: Key-Value Coding

Page 12: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Background: Key-Value Observing

Key-Value Coding (KVC) "itemPrice" ⇄ productModel.itemPrice

Key-Value Observing (KVO) - (void)itemPriceDidChange

Page 13: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

@interface TraditionalObserver : NSObject @property (weak) Product *observee; @property int changeCount; @end !@implementation TraditionalObserver !- (id)initWithProduct:(Product *)product { if (self = [super init]) { self.observee = product; [product addObserver:self forKeyPath:@"itemTitle" options:0 context:NULL]; [product addObserver:self forKeyPath:@"itemPrice" options:0 context:NULL]; } return self; } !- (void)dealloc { [self.observee removeObserver:self forKeyPath:@"itemTitle"]; [self.observee removeObserver:self forKeyPath:@"itemPrice"]; } !- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSAssert(object == self.observee, @"unexpected observee!"); self.changeCount++; // remember how many changes take place if ([keyPath isEqualToString:@"itemTitle"]) [self titleDidChange]; if ([keyPath isEqualToString:@"itemPrice"]) [self priceDidChange]; } !@end

Page 14: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

!@interface Product : NSObject @property (strong) NSString *itemTitle; @property double itemPrice; @end !@interface TraditionalObserver : NSObject @property (weak) Product *observee; @property int changeCount; @end !!Product *p = [[Product alloc] init]; TraditionalObserver *observer = [[TraditionalObserver alloc] initWithProduct:p]; NSAssert(observer.changeCount == 0, @"KVO change tracking"); !p.itemTitle = @"Paper"; p.itemPrice = 0.99; !NSAssert(observer.changeCount == 2, @"KVO change tracking");

Traditional Key-Value Observing

Page 15: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

#import <ReactiveCocoa/ReactiveCocoa.h> @interface Product : NSObject @property (strong) NSString *itemTitle; @property double itemPrice; @end !@interface TraditionalObserver : NSObject @property (weak) Product *observee; @property int changeCount; @end !!Product *product = [[Product alloc] init]; !NSUInteger __block priceChangeCount = 0; [RACObserve(product, itemPrice) subscribeNext:^(NSNumber *newPrice) { priceChangeCount++; }]; !NSAssert(priceChangeCount == 1, @"reactive change tracking (initial)"); product.itemPrice = 9.99; NSAssert(priceChangeCount == 2, @"reactive change tracking (next value)");

Reactive Subscription

Page 16: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Product *product = [[Product alloc] init]; !NSUInteger __block priceChangeCount = 0; ![RACObserve(product, itemPrice) subscribeNext:^(NSNumber *newPrice) { priceChangeCount++; }]; !RACSignal *priceSignal = [product rac_valuesForKeyPath:@"itemPrice" observer:self]; [priceSignal subscribeNext:^(NSNumber *newPrice) { priceChangeCount++; }]; !NSAssert(priceChangeCount == 2, @"reactive change tracking"); product.itemPrice = 9.99; NSAssert(priceChangeCount == 4, @"reactive change tracking");

RACObserve Macro Expanded

Page 17: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Background: Cocoa Bindings

Key-Value Coding (KVC) "itemPrice" ⇄ productModel.itemPrice

Key-Value Observing (KVO) - (void)itemPriceDidChange

Cocoa Bindings priceField.text ⇄ productModel.price

Page 18: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

@interface Product : NSObject @property (strong) NSString *itemTitle; @property double itemPrice; @end !@interface ProductViewController : NSViewController @property (strong) IBOutlet NSTextField *titleField; @end !Product *product = [[Product alloc] init]; !ProductViewController *controller = [[ProductViewController alloc] init]; NSTextField *field = controller.titleField; ![field bind:NSValueBinding toObject:product withKeyPath:@"itemTitle" options:nil]; !product.itemTitle = @"Paper"; NSAssert([field.stringValue isEqualToString:@"Paper"], @"good name"); !product.itemTitle = @"Paper++"; NSAssert([field.stringValue isEqualToString:@"Paper++"], @"great name!");

Cocoa Bindings

Page 19: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

@interface Product : NSObject @property (strong) NSString *itemTitle; @property double itemPrice; @end !@interface ProductViewController : NSViewController @property (strong) IBOutlet NSTextField *titleField; @end !Product *product = [[Product alloc] init]; UITextField *textField = [[UITextField alloc] init]; !RACChannelTo(textField, text) = RACChannelTo(product, itemTitle); !product.itemTitle = @"Paper"; NSAssert([[textField text] isEqualToString:@"Paper"], @"model ⇢ view"); !textField.text = @"Flappy Paper"; NSAssert([product.itemTitle isEqualToString:@"Flappy Paper"], @"view ⇠ model");

ReactiveCocoa Bindings

Page 20: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Product *product = [[Product alloc] init]; UITextField *textField = [[UITextField alloc] init]; !RACChannelTo(textField, text) = RACChannelTo(product, itemTitle); !RACKVOChannel *viewChannel = [[RACKVOChannel alloc] initWithTarget:textField keyPath:@"text" nilValue:nil]; !RACKVOChannel *modelChannel = [[RACKVOChannel alloc] initWithTarget:product keyPath:@"itemTitle" nilValue:nil]; !viewChannel[@"followingTerminal"] = modelChannel[@"followingTerminal"]; !!!product.itemTitle = @"Paper"; NSAssert([[textField text] isEqualToString:@"Paper"], @"model ⇢ view"); !textField.text = @"Flappy Paper Tweet Saga++ by Fifty-Four Ltd."; NSAssert([product.itemTitle isEqualToString:@"Flappy Paper Tweet Saga++ by Fifty-Four Ltd."], @"view ⇠ model");

RACChannelTo Macro Expanded

Page 21: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Cocoa Bindings vs. ReactiveCocoa

• No iOS support

• No block support

• Cumbersome value transformation

• No signal coalescing

• Difficult to debug

• Interface Builder

Page 22: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

User Interface & Commands

Page 23: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

ReactiveCocoa UI mechanisms

• Categories & Blocks

Page 24: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Commands

UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; ![button addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside]; !- (IBAction)buttonPressed:(id)sender { // do something }

Page 25: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Commands

UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; !button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) { // do something return [RACSignal empty]; }];

Page 26: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

UI Complexity Reduction

- (BOOL)isFormValid { return [self.usernameField.text length] > 0 && [self.emailField.text length] > 0 && [self.passwordField.text length] > 0 && [self.passwordField.text isEqual:self.passwordVerificationField.text]; } !#pragma mark - UITextFieldDelegate !- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { self.createButton.enabled = [self isFormValid]; return YES; }

Sample from http://nshipster.com/reactivecocoa/

Delegate Pattern:

Page 27: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

UI Complexity Reduction

RACSignal *formValid = [RACSignal combineLatest:@[ self.username.rac_textSignal, self.emailField.rac_textSignal, self.passwordField.rac_textSignal, self.passwordVerificationField.rac_textSignal ] reduce:^(NSString *name, NSString *email, NSString *pw1, NSString *pw2) { return @(name.length && email.length && pw1.length > 8 && [pw1 isEqual:pw2]); }]; RAC(self.createButton.enabled) = formValid;

Sample from http://nshipster.com/reactivecocoa/

Reactive Pattern:

Page 28: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Model-View-ViewModel

Page 29: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Collections & Sequences

Page 30: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

NSArray *values = @[ @"red", @"green", @"blue" ]; !NSArray *shortUppercaseColors = [[values filteredArrayUsingPredicate: [NSPredicate predicateWithBlock:^BOOL(NSString *color, NSDictionary *bindings) { return [color length] <= 4; }]] valueForKeyPath:@"uppercaseString"]; !NSAssert(([shortUppercaseColors isEqualToArray:@[ @"RED", @"BLUE" ]]), @"");

NSArray Filter & Map

Page 31: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

NSArray *values = @[ @"red", @"green", @"blue" ]; !RACSequence *seq = [[values.rac_sequence filter:^BOOL(NSString *color) { return [color length] <= 5; }] map:^id(NSString *color) { return [color uppercaseString]; }]; !NSAssert(([[seq array] isEqualToArray:@[ @"RED", @"BLUE" ]]), @"colors");

RACSequence Filter & Map

Sequences are evaluated lazily

Page 32: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

ReactiveCocoa Class Hierarchy

RACStream

RACSignal

RACChannelTerminal

RACChannel

RACSubject

RACSequence

Page 33: ReactiveCocoa - impathic Bindings vs. ReactiveCocoa

Resources

• https://github.com/ReactiveCocoa/ReactiveCocoa

• http://nshipster.com/reactivecocoa/

• http://www.teehanlax.com/blog/getting-started-with-reactivecocoa/

• http://cocoasamurai.blogspot.com/2013/03/basic-mvvm-with-reactivecocoa.html

• http://stackoverflow.com/questions/tagged/reactive-cocoa

• https://speakerdeck.com/joshaber/better-code-for-a-better-world