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
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
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
380
Tetris Masterclass
robb
0
560
Other Decks in Programming
See All in Programming
AIエージェント時代のコードレビューを設計する
nogu66
5
2.4k
LL言語やWebフレームワークのPostgreSQL対応 〜DBの機能がユーザーに届くまで〜
kentaroutakeda
0
130
Pythonの実行はどこまで賢くなったのか? CPythonとPyPyから見る最適化のしくみ
curekoshimizu
4
2.7k
PHPプロジェクトの結合バランスを可視化する #php_night
kajitack
0
200
Seeing Through Serverless: Observability for AWS Lambda with ADOT and CloudWatch Application Signals
seike460
PRO
1
110
週末にAI-DLCを本気で回したら$1,600溶けた
hbashimizu
0
120
Claude Codeを組織的に動かして月400PRを実現した話
happy_ryo
0
250
Hono + Inertia + React で LP を構築した話
oukayuka
2
200
ソフトウェアラスタライザ
fadis
1
780
異なる設計思想のフレームワークを経験して得た学び
amekuhideki
2
1.5k
이 함수, 실패하면 어떻게 되나요? null부터 Rich Errors까지, Kotlin 에러 처리 15년
haeti2
0
120
バグを直したら useEffect が消えた
colorful12
3
830
Featured
See All Featured
Everyday Curiosity
cassininazir
0
310
The World Runs on Bad Software
bkeepers
PRO
72
12k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
290
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
3k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
123
22k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
74
42k
Discover your Explorer Soul
emna__ayadi
2
1.3k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
490
A designer walks into a library…
pauljervisheath
211
24k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.5k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6.1k
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
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