Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
ReactiveCocoa NSSpain
Search
Robert Böhnke
September 18, 2013
Programming
2.5k
18
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
ReactiveCocoa NSSpain
My talk for NSSpain 2013 on ReactiveCocoa
Robert Böhnke
September 18, 2013
More Decks by Robert Böhnke
See All by Robert Böhnke
Brooklyn iOS Developer Meetup February 2014
robb
8
3.2k
Cocoa Kucha Berlin 2013
robb
2
2.1k
Underscore.m + Asterism
robb
4
1.3k
ReactiveCocoa
robb
19
2.8k
Super Mario Masterclass
robb
2
380
Tetris Masterclass
robb
0
550
Other Decks in Programming
See All in Programming
気圧・高度・GPSを記録&可視化するアプリ「Koudo」を作った話
hjmkth
1
340
壊れたパーサから始める関数型設計と構成的なパーサ #fp_matsuri
raiga0310
2
150
Go1.27で導入されるジェネリクスメソッドでできること
mackee
0
240
吝嗇家のためのAI活用 / AI development for miser - ChatGPT + Issue Driven Development
tooppoo
0
170
Contextとはなにか
chiroruxx
1
390
Mujeres en SEO Summit 2026 - Greatest Disaster Hits en Web Performance
guaca
0
230
Claude Opus 4.6以後の受託開発エンジニアの変化(Claude Code開発ノウハウ大公開スペシャルbyクラスメソッド)
iidatakuma
1
120
分散システム、なんですぐ死んでしまうん?耐障害性を高めたいあなたのためのレジリエンスパターン入門
mshibuya
7
3.2k
act1-costs.pdf
sumedhbala
0
170
The Bowling Game- From Imperative to Functional Programming - Part 1
philipschwarz
PRO
0
270
決定論的オーケストレーションの設計と実装 / Design and Implementation of Deterministic Orchestration
nrslib
4
1.6k
PHPだって関数型したい 〜できること、できないこと〜 / fp-in-php
jsoizo
0
140
Featured
See All Featured
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
330
Future Trends and Review - Lecture 12 - Web Technologies (1019888BNR)
signer
PRO
0
3.6k
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
220
Color Theory Basics | Prateek | Gurzu
gurzu
0
380
Building AI with AI
inesmontani
PRO
1
1.1k
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
2
4.1k
Visualization
eitanlees
152
17k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
55
3.4k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
3k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6k
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
240
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
280
Transcript
robb
[email protected]
ceterum_censeo
speakerdeck.com/robb/ reactivecocoa-nsspain
Let's talk about ReactiveCocoa
Let's talk about ReactiveCocoa state
evil √
Have you tried turning it off and on again?
Have you tried turning it off and on again? Have
you tried turning it off and on again?
Start Okay Fail
Start Okay Fail power-cycle power-cycle
state
state
duh! " " you say
e.g.
@property (readwrite) BOOL walks; @property (readwrite) BOOL quaks; @property (readonly)
BOOL duck;
duck = walks ∧ quaks
- (void)setWalks:(BOOL)walks { _walks = walks; self.duck = walks &&
self.quacks; }
- (void)setWalks:(BOOL)walks { _walks = walks; self.duck = walks &&
self.quacks; } - (void)setQuacks:(BOOL)quacks { _quacks = quacks; self.duck = quacks && self.walks; }
- (void)setWalks:(BOOL)walks { _walks = walks; self.duck = walks &&
self.quacks; } - (void)setQuacks:(BOOL)quacks { _quacks = quacks; self.duck = quacks && self.walks; }
- (BOOL)duck { return self.walks && self.quacks; }
- (NSSet *)keyPathsForValuesAffectingDuck { return [NSSet setWithArray:@[ @"walks", @"quacks" ]];
} - (BOOL)duck { return self.walks && self.quacks; }
- (BOOL)duck { return self.walks && self.quacks; } // duck
= walks ∧ quaks
ReactiveCocoa
Functional Reactive Programming
Signals instead of variables
Pipes, not boxes
wtf? " " you may be thinking
e.g.
RAC(self, duck) = [RACSignal combineLatest:@[ RACObserve(self, walks), RACObserve(self, quacks) ]
reduce:^(NSNumber *walks, NSNumber *quacks) { return @(walks.boolValue && quacks.boolValue); }]:
RAC(self, duck) = [RACSignal combineLatest:@[ RACObserve(self, walks), RACObserve(self, quacks) ]
reduce:^(NSNumber *walks, NSNumber *quacks) { return @(walks.boolValue && quacks.boolValue); }]:
RAC(self, duck) = [RACSignal combineLatest:@[ RACObserve(self, walks), RACObserve(self, quacks) ]
reduce:^(NSNumber *walks, NSNumber *quacks) { return @(walks.boolValue && quacks.boolValue); }]:
RAC(self, duck) = [RACSignal combineLatest:@[ RACObserve(self, walks), RACObserve(self, quacks) ]
reduce:^(NSNumber *walks, NSNumber *quacks) { return @(walks.boolValue && quacks.boolValue); }]:
RAC(self, duck) = [RACSignal combineLatest:@[ RACObserve(self, walks), RACObserve(self, quacks) ]
reduce:^(NSNumber *walks, NSNumber *quacks) { return @(walks.boolValue && quacks.boolValue); }]:
// and it even does KVO! RAC(self, duck) = [RACSignal
combineLatest:@[ RACObserve(self, walks), RACObserve(self, quacks) ] reduce:^(NSNumber *walks, NSNumber *quacks) { return @(walks.boolValue && quacks.boolValue); }]:
& walks talks duck
& walks talks duck
& walks talks duck
& walks talks duck
& walks talks duck
RACSignal
-(RACDisposable * )subscribe:(id<RACSubscriber> )obj; RACSignal
<RACSubscriber>
- (void)sendNext:(id)value; <RACSubscriber>
- (void)sendNext:(id)value; - (void)sendCompleted; <RACSubscriber>
- (void)sendNext:(id)value; - (void)sendCompleted; - (void)sendError:(NSError *)error; <RACSubscriber>
so what?
Powerful toolset
map
filter
fold
write declarative code
e.g.
Your name Your email address Sign Up Newsletter
Newsletter Your name Your email address Sign Up
Newsletter Your email address Sign Up robb
Newsletter Sign Up robb robb
Newsletter Sign Up robb @robb.is Sign Up robb
Your name Your email address Sign Up validate that looks
familiar! Newsletter
- (void)viewDidLoad { [super viewDidLoad]; [self.username addTarget:self action:@selector(textFieldTextDidChange:) forControlEvents:UIControlEventAllEditingEvents]; [self.email
addTarget:self action:@selector(textFieldTextDidChange:) forControlEvents:UIControlEventAllEditingEvents]; } help!
- (void)textFieldTextDidChange:(UITextField *)field { BOOL validUsername = self.username.text.length > 0;
NSRange at = [self.email.text rangeOfString:@"@"]; BOOL validEmail = at.location != NSNotFound; self.signupButton.enabled = validUsername && validEmail; }
- (void)textFieldTextDidChange:(UITextField *)field { BOOL validUsername = self.username.text.length > 0;
NSRange at = [self.email.text rangeOfString:@"@"]; BOOL validEmail = at.location != NSNotFound; self.signupButton.enabled = validUsername && validEmail; }
ReactiveCocoa
RAC(self.signUpButton, enabled) = [RACSignal combineLatest:@[ self.name.rac_textSignal self.email.rac_textSignal ] reduce:^(NSString *name,
NSString *email) { NSRange at = [email rangeOfString:@"@"]; return @(at.location != NSNotFound && name.length > 0); }]:
RAC(self.signUpButton, enabled) = [RACSignal combineLatest:@[ self.name.rac_textSignal self.email.rac_textSignal ] reduce:^(NSString *name,
NSString *email) { NSRange at = [email rangeOfString:@"@"]; return @(at.location != NSNotFound && name.length > 0); }]:
RAC(self.signUpButton, enabled) = [RACSignal combineLatest:@[ self.name.rac_textSignal self.email.rac_textSignal ] reduce:^(NSString *name,
NSString *email) { NSRange at = [email rangeOfString:@"@"]; return @(at.location != NSNotFound && name.length > 0); }]:
RAC(self.signUpButton, enabled) = [RACSignal combineLatest:@[ self.name.rac_textSignal self.email.rac_textSignal ] reduce:^(NSString *name,
NSString *email) { NSRange at = [email rangeOfString:@"@"]; return @(at.location != NSNotFound && name.length > 0); }]:
Your name Your email address Sign Up validate Newsletter
Newsletter Your name Your email address Sign Up
Newsletter Sign Up robb
Newsletter Sign Up robb
[email protected]
Sign Up
Let's talk about
Let's talk about Asynchrony
[client logInWithSuccess:^{ [client loadMeUserWithSuccess:^(SPUser *me) { [client loadFilesForUser:me withSuccess:^(NSArray *files)
{ NSLog(@"Help me, " "I'm trapped in callback hell!"); } error:^(NSError *error) { // Handle failure } } error:^(NSError *error) { // Handle failure }]; } error:^(NSError *error) { // Handle failure }]; omg, why?!
Let's fix this
1. HTTP Client
@interface SPHTTPClient : AFHTTPClient - (RACSignal *)enqueueRequest:(NSURLRequest *)req; @end
[self enqueueHTTPRequestOperation:operation]; - (RACSignal *)enqueueRequest:(NSURLRequest *)req { return [RACSignal createSignal:^(id<RACSubscriber>
subscriber) { AFHTTPRequestOperation *operation; operation = [self HTTPRequestOperationWithRequest:req success:^(id operation, id response) { [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^(id operation, NSError *error) { [subscriber sendError:error]; }];
[self enqueueHTTPRequestOperation:operation]; - (RACSignal *)enqueueRequest:(NSURLRequest *)req { return [RACSignal createSignal:^(id<RACSubscriber>
subscriber) { AFHTTPRequestOperation *operation; operation = [self HTTPRequestOperationWithRequest:req success:^(id operation, id response) { [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^(id operation, NSError *error) { [subscriber sendError:error]; }];
- (RACSignal *)enqueueRequest:(NSURLRequest *)req { return [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
AFHTTPRequestOperation *operation; operation = [self HTTPRequestOperationWithRequest:req success:^(id operation, id response) { [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^(id operation, NSError *error) { [subscriber sendError:error]; }]; [self enqueueHTTPRequestOperation:operation];
- (RACSignal *)enqueueRequest:(NSURLRequest *)req { return [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
AFHTTPRequestOperation *operation; operation = [self HTTPRequestOperationWithRequest:req success:^(id operation, id response) { [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^(id operation, NSError *error) { [subscriber sendError:error]; }]; [self enqueueHTTPRequestOperation:operation];
- (RACSignal *)enqueueRequest:(NSURLRequest *)req { return [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
AFHTTPRequestOperation *operation; operation = [self HTTPRequestOperationWithRequest:req success:^(id operation, id response) { [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^(id operation, NSError *error) { [subscriber sendError:error]; }]; [self enqueueHTTPRequestOperation:operation];
[self enqueueHTTPRequestOperation:operation]; return [RACDisposable disposableWithBlock:^{ [operation cancel]; }]; }]; createSignal:^(id<RACSubscriber>
subscriber) { AFHTTPRequestOperation *operation; operation = [self HTTPRequestOperationWithRequest:req success:^(id operation, id response) { [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^(id operation, NSError *error) { [subscriber sendError:error]; }];
[self enqueueHTTPRequestOperation:operation]; return [RACDisposable disposableWithBlock:^{ [operation cancel]; }]; }]; createSignal:^(id<RACSubscriber>
subscriber) { AFHTTPRequestOperation *operation; operation = [self HTTPRequestOperationWithRequest:req success:^(id operation, id response) { [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^(id operation, NSError *error) { [subscriber sendError:error]; }];
2. API interaction
- (RACSignal *)fetchMeUser return [[self.client getPath:@"/me"] flattenMap:^(id JSON) { NSError
*error; SPUser *user = [SPUser userWithJSON:JSON error:&error]; if (user == nil) { return [RACSignal error:error]; } else { return [RACSignal return:user]; } }]; }
- (RACSignal *)fetchMeUser return [[self.client getPath:@"/me"] flattenMap:^(id JSON) { NSError
*error; SPUser *user = [SPUser userWithJSON:JSON error:&error]; if (user == nil) { return [RACSignal error:error]; } else { return [RACSignal return:user]; } }]; }
- (RACSignal *)fetchMeUser return [[self.client getPath:@"/me"] flattenMap:^(id JSON) { NSError
*error; SPUser *user = [SPUser userWithJSON:JSON error:&error]; if (user == nil) { return [RACSignal error:error]; } else { return [RACSignal return:user]; } }]; }
- (RACSignal *)fetchMeUser return [[self.client getPath:@"/me"] flattenMap:^(id JSON) { NSError
*error; SPUser *user = [SPUser userWithJSON:JSON error:&error]; if (user == nil) { return [RACSignal error:error]; } else { return [RACSignal return:user]; } }]; }
- (RACSignal *)fetchMeUser return [[self.client getPath:@"/me"] flattenMap:^(id JSON) { NSError
*error; SPUser *user = [SPUser userWithJSON:JSON error:&error]; if (user == nil) { return [RACSignal error:error]; } else { return [RACSignal return:user]; } }]; } check out Mantle!
- (RACSignal *)fetchMeUser return [[self.client getPath:@"/me"] flattenMap:^(id JSON) { NSError
*error; SPUser *user = [SPUser userWithJSON:JSON error:&error]; if (user == nil) { return [RACSignal error:error]; } else { return [RACSignal return:user]; } }]; }
- (RACSignal *)fetchMeUser return [[self.client getPath:@"/me"] flattenMap:^(id JSON) { NSError
*error; SPUser *user = [SPUser userWithJSON:JSON error:&error]; if (user == nil) { return [RACSignal error:error]; } else { return [RACSignal return:user]; } }]; }
3. Putting it together
[[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {
return [client fetchFilesForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); } error:^(NSError *error) { // Handle error }];
[[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {
return [client fetchFilesForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); } error:^(NSError *error) { // Handle error }];
[[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {
return [client fetchFilesForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); } error:^(NSError *error) { // Handle error }];
[[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {
return [client fetchFilesForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); } error:^(NSError *error) { // Handle error }];
[[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {
return [client fetchFilesForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); } error:^(NSError *error) { // Handle error }];
[[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {
return [client fetchFilesForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); } error:^(NSError *error) { // Handle error }];
[[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {
return [client fetchFilesForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); } error:^(NSError *error) { // Handle error }];
UI&IO
UI+IO
Your name Your email address Sign Up Newsletter
Your name Your email address Sign Up validate Newsletter
Your name Your email address Sign Up validate Newsletter
, why me? oh boy
self.name.rac_textSignal self.email.rac_textSignal reduce:^(NSString *name, NSString *email) { NSRange at =
[email rangeOfString:@"@"]; return @(at.location != NSNotFound && name.length > 0); }]; RAC(self.signUpButton, enabled) = [RACSignal combineLatest:@[ ]
self.name.rac_textSignal self.email.rac_textSignal reduce:^(NSString *name, NSString *email) { NSRange at =
[email rangeOfString:@"@"]; return @(at.location != NSNotFound && name.length > 0); }]; RAC(self.signUpButton, enabled) = [RACSignal combineLatest:@[ ]
validName, validEmail reduce:^(NSString *name, NSString *email) { NSRange at =
[email rangeOfString:@"@"]; return @(at.location != NSNotFound && name.length > 0); }]; reduce:^(NSString *name, NSString *email) { NSRange at = [email rangeOfString:@"@"]; return @(at.location != NSNotFound && name.length > 0); }]; RAC(self.signUpButton, enabled) = [RACSignal combineLatest:@[ ]
reduce:^(NSString *name, NSString *email) { NSRange at = [email rangeOfString:@"@"];
return @(at.location != NSNotFound && name.length > 0); }]; validName, validEmail RAC(self.signUpButton, enabled) = [RACSignal combineLatest:@[ ]
reduce:^(NSNumber *name, NSNumber *email) { return @(name.boolValue && email.boolValue); }];
validName, validEmail map:^(NSString *name) { return @(name.length > 0); }]; RAC(self.signUpButton, enabled) = [RACSignal combineLatest:@[ ]
RACSignal *validName = [self.name.rac_textSignal map:^(NSString *name) { return @(name.length >
0); }]; reduce:^(NSNumber *name, NSNumber *email) { validName, validEmail RAC(self.signUpButton, enabled) = [RACSignal combineLatest:@[ ] RACSignal *validEmail = [[self.email.rac_textSignal map:^(NSString *email) { return [[client validateEmail:email] startWith:@NO]; }] switchToLatest];
RACSignal *validName = [self.name.rac_textSignal map:^(NSString *name) { return @(name.length >
0); }]; RACSignal *validEmail = [[self.email.rac_textSignal map:^(NSString *email) { return [[client validateEmail:email] startWith:@NO]; }] switchToLatest];
RACSignal *validEmail = [[self.email.rac_textSignal map:^(NSString *email) { return [[client validateEmail:email]
startWith:@NO]; }] switchToLatest]; RACSignal *validName = [self.name.rac_textSignal map:^(NSString *name) { return @(name.length > 0); }];
RACSignal *validEmail = [[self.email.rac_textSignal map:^(NSString *email) { return [[client validateEmail:email]
startWith:@NO]; }] switchToLatest]; RACSignal *validName = [self.name.rac_textSignal map:^(NSString *name) { return @(name.length > 0); }];
Your name Your email address Sign Up validate Newsletter
Newsletter Your name Your email address Sign Up
Newsletter Sign Up robb
Newsletter Sign Up robb
[email protected]
Newsletter Sign Up robb
[email protected]
Sign Up
Recap
Recap + Declarative code
Recap + Declarative code + Easy handling of asynchronous tasks
Recap + Declarative code + Easy handling of asynchronous tasks
+ Great Composition
Recap + Declarative code + Easy handling of asynchronous tasks
+ Great Composition − Conceptual overhead
Recap + Declarative code + Easy handling of asynchronous tasks
+ Great Composition − Conceptual overhead − Debugging can get tricky
Recap (dtrace support coming soon) + Declarative code + Easy
handling of asynchronous tasks + Great Composition − Conceptual overhead − Debugging can get tricky
Qs?
Thanks!
iPhone 5 Setch Template courtesy of Denis Rojčyk Cloud icon
curtesy of Dmitry Baranovskiy, from The Noun Project