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
Brooklyn iOS Developer Meetup February 2014
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Robert Böhnke
February 19, 2014
Programming
3.2k
8
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Brooklyn iOS Developer Meetup February 2014
Robert Böhnke
February 19, 2014
More Decks by Robert Böhnke
See All by Robert Böhnke
Cocoa Kucha Berlin 2013
robb
2
2.1k
ReactiveCocoa NSSpain
robb
18
2.5k
Underscore.m + Asterism
robb
4
1.3k
ReactiveCocoa
robb
19
2.8k
Super Mario Masterclass
robb
2
370
Tetris Masterclass
robb
0
550
Other Decks in Programming
See All in Programming
jQueryをバージョンアップする前に使いたいjQuery Migrate
matsuo_atsushi
0
330
Developing with AI Agents — Codex, Claude Code & Cowork Practical Guide
x5gtrn
PRO
0
1.3k
エンジニアと一緒にテストコードの設計と実装を改善した話
mototakatsu
0
120
「エンジニアインターン、どうやって取った?」準備のリアルを語るLT会 Progate BAR
akiomatic
0
130
キャリア迷子上等 ─ "ない道"は自分で作ればいい
16bitidol
3
2k
運用エージェントは "作る" から "育てる" へ - 記憶と自己進化の3層設計パターン / self-evolving-agents-three-layer-agent-design
gawa
12
3.6k
AI時代の仕事技芸論 — ソフトウェア開発で「遊ぶように働く」職人的熟達のすすめ
kuranuki
2
660
AIチームを指揮するOSS「TAKT」活用術 / How to Use “TAKT,” an OSS Tool for Orchestrating AI Teams
nrslib
6
890
JavaDoc 再入門
nagise
0
330
OSもどきOS
arkw
0
560
生成AI時代にこそ効くGo | Why Go Works in the Age of Generative AI
mom0tomo
8
3.2k
例外の正しい扱い方 そのエラー try-catchして大丈夫?
jinwatanabe
0
220
Featured
See All Featured
Navigating Weather and Climate Data
rabernat
0
220
AI Search: Where Are We & What Can We Do About It?
aleyda
0
7.6k
The Curse of the Amulet
leimatthew05
1
13k
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
First, design no harm
axbom
PRO
2
1.2k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
940
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.1k
Scaling GitHub
holman
464
140k
The Pragmatic Product Professional
lauravandoore
37
7.3k
Why Our Code Smells
bkeepers
PRO
340
58k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
Future Trends and Review - Lecture 12 - Web Technologies (1019888BNR)
signer
PRO
0
3.6k
Transcript
robb
[email protected]
ceterum_censeo
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)setQuacks:(BOOL)quacks { _quacks = quacks;
self.duck = quacks && self.walks; } - ( _walks = walks; self.duck = walks }
- (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); }];
& 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.
Newsletter Your name Your email address Sign Up Brooklyn 4:00
PM 100%
Newsletter Your email address Sign Up robb Brooklyn 4:00 PM
100%
Newsletter Sign Up robb robb Brooklyn 4:00 PM 100%
Newsletter Sign Up robb @robb.is Sign Up robb Brooklyn 4:00
PM 100%
Your name Your email address Sign Up validate Newsletter
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 Brooklyn 4:00
PM 100%
Newsletter Your email address Sign Up robb Brooklyn 4:00 PM
100%
Newsletter Sign Up robb Sign Up
[email protected]
Brooklyn 4:00 PM
100%
Let's talk about Asynchrony
[client logInWithSuccess:^{ [client loadMeUserWithSuccess:^(SPUser *me) { [client loadNewslettersForUser:me withSuccess:^(NSArray *newsletters)
{ NSLog(@"Help me, " "I'm trapped in callback hell!"); } error:^(NSError *error) { // Handle failure } } error:^(NSError *error) { // Handle failure }]; } error:^(NSError *error) { // Handle failure }];
[client logInWithSuccess:^{ [client loadMeUserWithSuccess:^(SPUser *me) { [client loadNewslettersForUser:me withSuccess:^(NSArray *newsletters)
{ 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]; }];
- (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]; ! - (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]; !
[self enqueueHTTPRequestOperation:operation]; ! return [RACDisposable disposableWithBlock:^{ [operation cancel]; }]; }];
success:^( [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^( [subscriber sendError:error]; }];
[self enqueueHTTPRequestOperation:operation]; ! return [RACDisposable disposableWithBlock:^{ [operation cancel]; }]; }];
success:^( [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^( [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 fetchNewslettersForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); }} error:^(NSError *error) { // Handle error }];
[[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {
return [client fetchNewslettersForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); }} error:^(NSError *error) { // Handle error }];
[[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {
return [client fetchNewslettersForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); }} error:^(NSError *error) { // Handle error }];
[[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {
return [client fetchNewslettersForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); }} error:^(NSError *error) { // Handle error }];
[[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {
return [client fetchNewslettersForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); }} error:^(NSError *error) { // Handle error }];
[[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {
return [client fetchNewslettersForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); }} error:^(NSError *error) { // Handle error }];
UI&IO
UI+IO
Newsletter Your name Your email address Sign Up Sign Up
Brooklyn 4:00 PM 100%
Your name Your email address Sign Up validate Newsletter
Your name Your email address Sign Up validate Newsletter
oh boy
oh boy, why me?
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:@[ ]]
validName, validEmail 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); }];
RAC(self.signUpButton, enabled) = [RACSignal combineLatest:@[ ]]
validName, validEmail reduce:^(NSNumber *name, NSNumber *email) { return @(name.boolValue && email.boolValue); }]; }];
RAC(self.signUpButton, enabled) = [RACSignal combineLatest: validName,
validEmail reduce:^(NSNumber *name, NSNumber *email) { RACSignal *validName = [self.name.rac_textSignal map:^(NSString *name) { return @(name.length > 0); }]; switchToLatest];
RACSignal *validName = [self.name.rac_textSignal map:^(NSString *name) { return }]
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 }]
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 }]
RACSignal *validEmail = [[self.email.rac_textSignal map:^(NSString *email) { return [[client validateEmail:email] startWith:@NO]; }] switchToLatest];
Your name Your email address Sign Up validate Newsletter
Newsletter Your name Your email address Sign Up Brooklyn 4:00
PM 100%
Newsletter Your email address Sign Up robb Brooklyn 4:00 PM
100%
Newsletter Sign Up robb
[email protected]
Brooklyn 4:00 PM 100%
Newsletter Sign Up robb Sign Up
[email protected]
Brooklyn 4:00 PM
100%
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
+ Declarative code + Easy handling of asynchronous tasks +
Great Composition − Conceptual overhead − Debugging can get tricky Recap (dtrace support)
Qs?
Thanks!
iPhone 5 Sketch Template courtesy of Denis Rojčyk ! Cloud
icon curtesy of Dmitry Baranovskiy, from The Noun Project