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
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
AWS DevOps Agentで インシデント対応をAIに任せたい
honmarkhunt
7
2.6k
LL言語やWebフレームワークのPostgreSQL対応 〜DBの機能がユーザーに届くまで〜
kentaroutakeda
0
140
【デモ】Kiroで体験する仕様駆動開発|設計からコーディングまでAIと進める開発フロー
cmkudo
0
600
AI Agent時代のリアーキテクチャ戦略と実践
hokaccha
1
710
自分的「カンファレンスの楽しみ方」
syumai
0
190
Claude Codeを組織的に動かして月400PRを実現した話
happy_ryo
0
260
LoopHub - ローカルで動く GitHub で、AI と共同開発
jugyo
1
460
思考垂れ流し開発 ~音声入力 × AIエージェント × 開発ハーネスによる試行錯誤~
npostring
0
950
AIに既存システムを理解させる技術 ~レガシーを見捨てないハーネスエンジニアリング入門~
ochtum
0
210
世界の中心で、AI(App Intents)をさけぶ ー App Intents中心設計の実践ガイド
touyou
0
170
tsc.rip を支える技術 / Kyoto.なんか #8
susisu
0
4.3k
AI に Inclusive UI を書かせよう — Design Rules Skill で Compose UI を作り直す
theoriatec2024
1
460
Featured
See All Featured
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.5k
Lessons Learnt from Crawling 1000+ Websites
charlesmeaden
PRO
1
1.6k
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
270
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Believing is Seeing
oripsolob
1
210
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
0
600
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
290
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
560
Facilitating Awesome Meetings
lara
57
7.1k
What does AI have to do with Human Rights?
axbom
PRO
1
2.4k
The Cult of Friendly URLs
andyhume
79
7k
The Spectacular Lies of Maps
axbom
PRO
1
980
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