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
18
2.4k
ReactiveCocoa NSSpain
My talk for NSSpain 2013 on ReactiveCocoa
Robert Böhnke
September 18, 2013
Tweet
Share
More Decks by Robert Böhnke
See All by Robert Böhnke
Brooklyn iOS Developer Meetup February 2014
robb
8
3.1k
Cocoa Kucha Berlin 2013
robb
2
2.1k
Underscore.m + Asterism
robb
4
1.2k
ReactiveCocoa
robb
19
2.7k
Super Mario Masterclass
robb
2
320
Tetris Masterclass
robb
0
530
Other Decks in Programming
See All in Programming
RailsGirls IZUMO スポンサーLT
16bitidol
0
190
おやつのお供はお決まりですか?@WWDC25 Recap -Japan-\(region).swift
shingangan
0
140
GitHub Copilot and GitHub Codespaces Hands-on
ymd65536
2
150
Android 16KBページサイズ対応をはじめからていねいに
mine2424
0
240
Claude Code + Container Use と Cursor で作る ローカル並列開発環境のススメ / ccc local dev
kaelaela
12
6.7k
状態遷移図を書こう / Sequence Chart vs State Diagram
orgachem
PRO
2
170
新メンバーも今日から大活躍!SREが支えるスケールし続ける組織のオンボーディング
honmarkhunt
5
8.2k
iOS 26にアップデートすると実機でのHot Reloadができない?
umigishiaoi
0
140
AIエージェントはこう育てる - GitHub Copilot Agentとチームの共進化サイクル
koboriakira
0
620
A full stack side project webapp all in Kotlin (KotlinConf 2025)
dankim
0
130
イベントストーミング図からコードへの変換手順 / Procedure for Converting Event Storming Diagrams to Code
nrslib
2
970
Quand Symfony, ApiPlatform, OpenAI et LangChain s'allient pour exploiter vos PDF : de la théorie à la production…
ahmedbhs123
0
210
Featured
See All Featured
Into the Great Unknown - MozCon
thekraken
40
1.9k
Making Projects Easy
brettharned
116
6.3k
Making the Leap to Tech Lead
cromwellryan
134
9.4k
A Modern Web Designer's Workflow
chriscoyier
695
190k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
30
2.1k
A better future with KSS
kneath
238
17k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
15
1.6k
Intergalactic Javascript Robots from Outer Space
tanoku
271
27k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
8
700
YesSQL, Process and Tooling at Scale
rocio
173
14k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
Building a Modern Day E-commerce SEO Strategy
aleyda
42
7.4k
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