Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Brooklyn iOS Developer Meetup February 2014
Search
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
390
Tetris Masterclass
robb
0
560
Other Decks in Programming
See All in Programming
re:Inventに行く前に知っておきたい現地参加のノウハウ
nokomoro3
0
420
RAG の “R” を Swift で覗いてみる 〜「意味から探す」検索の仕組み〜
nao_randd
0
160
月間100万人のサービス刷新で、 技術選定をHonoに振り切った話
yutobara
8
6.6k
コードで差別化できない時代の ソフトウェアエンジニアリング / Software Engineering in an Era Where Code Doesn’t Differentiate
kishida
2
180
Long Living Tokens, Die, Die, Die! (or: The One Ring is Banned in Modern)
sylph01
0
300
コーディング自動化の次へ。人の介入をゼロにするファクトリーとサポートAIによる実践
timakin
4
3.5k
ActionController meets Protocol Buffers
sorah
0
520
自サークルのIdPからログアウトできるようになるまで
taiseiue
1
140
IBM Bob Dojo #1 仕様駆動開発入門
oniak3ibm
PRO
0
390
JIS keyboard support in Omarchy
lef237
0
110
パスワード再設定メールのURLについて知っておこう
uyuki234
0
160
Web Framework Benchmark Battle 2026, Any%
a_matsuda
2
2k
Featured
See All Featured
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
1.1k
Being A Developer After 40
akosma
92
590k
Unsuck your backbone
ammeep
672
58k
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.4k
Paper Plane (Part 1)
katiecoart
PRO
2
12k
BBQ
matthewcrist
90
10k
Deep Space Network (abreviated)
tonyrice
0
370
Thoughts on Productivity
jonyablonski
76
5.4k
Utilizing Notion as your number one productivity tool
mfonobong
4
620
Code Reviewing Like a Champion
maltzj
529
40k
sira's awesome portfolio website redesign presentation
elsirapls
0
450
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
56
3.5k
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