Pro Yearly is on sale from $80 to $50! »

Brooklyn iOS Developer Meetup February 2014

Brooklyn iOS Developer Meetup February 2014

B00d4ed5d1e6cb9fc840167820247515?s=128

Robert Böhnke

February 19, 2014
Tweet

Transcript

  1. 
 robb robb@robb.is ceterum_censeo

  2. Let's talk about ReactiveCocoa

  3. Let's talk about ReactiveCocoa state

  4. evil √

  5. Have you tried turning it off and on again?

  6. Have you tried turning it off and on again? Have

    you tried turning it off and on again?
  7. Start Okay Fail

  8. Start Okay Fail power-cycle power-cycle

  9. state

  10. state

  11. duh! " " you say

  12. e.g.

  13. @property (readwrite) BOOL walks;
 @property (readwrite) BOOL quaks; ! @property

    (readonly) BOOL duck;
  14. duck = walks ∧ quaks

  15. 
 
 ! ! - (void)setQuacks:(BOOL)quacks { _quacks = quacks;

    
 self.duck = quacks && self.walks;
 } - ( _walks = walks; 
 self.duck = walks }
  16. - (BOOL)duck { return self.walks && self.quacks;
 } // duck

    = walks ∧ quaks
  17. ReactiveCocoa

  18. Functional Reactive
 Programming

  19. Signals instead of variables

  20. Pipes, not boxes

  21. wtf? " " you may be thinking

  22. e.g.

  23. RAC(self, duck) = [RACSignal
 combineLatest:@[
 RACObserve(self, walks),
 RACObserve(self, quacks)
 ]]


    reduce:^(NSNumber *walks, NSNumber *quacks) {
 return @(walks.boolValue &&
 quacks.boolValue);
 }];
  24. RAC(self, duck) = [RACSignal
 combineLatest:@[
 RACObserve(self, walks),
 RACObserve(self, quacks)
 ]]


    reduce:^(NSNumber *walks, NSNumber *quacks) {
 return @(walks.boolValue &&
 quacks.boolValue);
 }];
  25. RAC(self, duck) = [RACSignal
 combineLatest:@[
 RACObserve(self, walks),
 RACObserve(self, quacks)
 ]]


    reduce:^(NSNumber *walks, NSNumber *quacks) {
 return @(walks.boolValue &&
 quacks.boolValue);
 }];
  26. RAC(self, duck) = [RACSignal
 combineLatest:@[
 RACObserve(self, walks),
 RACObserve(self, quacks)
 ]]


    reduce:^(NSNumber *walks, NSNumber *quacks) {
 return @(walks.boolValue &&
 quacks.boolValue);
 }];
  27. & walks talks duck

  28. & walks talks duck

  29. & walks talks duck

  30. & walks talks duck

  31. & walks talks duck

  32. RACSignal

  33. -(RACDisposable *)subscribe:(id<RACSubscriber>)obj; RACSignal

  34. <RACSubscriber>

  35. - (void)sendNext:(id)value; <RACSubscriber>

  36. - (void)sendNext:(id)value; - (void)sendCompleted; <RACSubscriber>

  37. - (void)sendNext:(id)value; - (void)sendCompleted; - (void)sendError:(NSError *)error; <RACSubscriber>

  38. so what?

  39. Powerful toolset

  40. map

  41. filter

  42. fold

  43. write declarative code

  44. e.g.

  45. Newsletter Your name Your email address Sign Up Brooklyn 4:00

    PM 100%
  46. Newsletter Your email address Sign Up robb Brooklyn 4:00 PM

    100%
  47. Newsletter Sign Up robb robb Brooklyn 4:00 PM 100%

  48. Newsletter Sign Up robb @robb.is Sign Up robb Brooklyn 4:00

    PM 100%
  49. Your name Your email address Sign Up validate Newsletter

  50. Your name Your email address Sign Up validate that looks

    familiar! Newsletter
  51. - (void)viewDidLoad {
 [super viewDidLoad];
 [self.username
 addTarget:self
 action:@selector(textFieldTextDidChange:)
 forControlEvents:UIControlEventAllEditingEvents];
 [self.email


    addTarget:self
 action:@selector(textFieldTextDidChange:)
 forControlEvents:UIControlEventAllEditingEvents];
 } help!
  52. - (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;
 }
  53. - (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;
 }
  54. ReactiveCocoa

  55. 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);
 }]:
  56. 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);
 }]:
  57. 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);
 }]:
  58. 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);
 }]:
  59. Your name Your email address Sign Up validate Newsletter

  60. Newsletter Your name Your email address Sign Up Brooklyn 4:00

    PM 100%
  61. Newsletter Your email address Sign Up robb Brooklyn 4:00 PM

    100%
  62. Newsletter Sign Up robb Sign Up robb@robb.is Brooklyn 4:00 PM

    100%
  63. Let's talk about Asynchrony

  64. [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 }];
  65. [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?!
  66. Let's fix this

  67. 1. HTTP Client

  68. @interface SPHTTPClient : AFHTTPClient
 
 - (RACSignal *)enqueueRequest:(NSURLRequest *)req;
 


    @end
  69. [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]; }];
  70. - (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]; !
  71. [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]; }];
  72. - (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]; !
  73. - (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]; !
  74. [self enqueueHTTPRequestOperation:operation]; ! return [RACDisposable disposableWithBlock:^{ [operation cancel]; }]; }];

    success:^( [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^( [subscriber sendError:error]; }];
  75. [self enqueueHTTPRequestOperation:operation]; ! return [RACDisposable disposableWithBlock:^{ [operation cancel]; }]; }];

    success:^( [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^( [subscriber sendError:error]; }];
  76. 2. API interaction

  77. - (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];
 }} }];
 }}
  78. - (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];
 }} }];
 }}
  79. - (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];
 }} }];
 }}
  80. - (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];
 }} }];
 }}
  81. - (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!

  82. - (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];
 }} }];
 }}
  83. - (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];
 }} }];
 }}
  84. 3. Putting it together

  85. [[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {

    return [client fetchNewslettersForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); }}
 error:^(NSError *error) { // Handle error }];
  86. [[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {

    return [client fetchNewslettersForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); }}
 error:^(NSError *error) { // Handle error }];
  87. [[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {

    return [client fetchNewslettersForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); }}
 error:^(NSError *error) { // Handle error }];
  88. [[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {

    return [client fetchNewslettersForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); }}
 error:^(NSError *error) { // Handle error }];
  89. [[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {

    return [client fetchNewslettersForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); }}
 error:^(NSError *error) { // Handle error }];
  90. [[[[client logIn] then:^{ return [client fetchMeUser]; }] flattenMap:^(SPUser *me) {

    return [client fetchNewslettersForUser:me]; }] subscribeNext:^(NSArray *files) { NSLog(@"Files: %@", files); }}
 error:^(NSError *error) { // Handle error }];
  91. UI&IO

  92. UI+IO

  93. Newsletter Your name Your email address Sign Up Sign Up

    Brooklyn 4:00 PM 100%
  94. Your name Your email address Sign Up validate Newsletter

  95. Your name Your email address Sign Up validate Newsletter

  96. oh boy

  97. oh boy, why me?

  98. 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);
 }];
  99. 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);
 }];
  100. RAC(self.signUpButton, enabled) = [RACSignal
 combineLatest:@[
 
 
 ]] 
 


    
 
 validName,
 validEmail reduce:^(NSString *name, NSString *email) {
 NSRange at = [email rangeOfString:@"@"];
 return @(at.location != NSNotFound &&
 name.length > 0);
 }];
  101. RAC(self.signUpButton, enabled) = [RACSignal
 combineLatest:@[
 
 
 ]] 
 


    
 
 validName,
 validEmail reduce:^(NSString *name, NSString *email) {
 NSRange at = [email rangeOfString:@"@"];
 return @(at.location != NSNotFound &&
 name.length > 0);
 }];
  102. RAC(self.signUpButton, enabled) = [RACSignal
 combineLatest:@[
 
 
 ]] 
 


    
 
 validName,
 validEmail reduce:^(NSNumber *name, NSNumber *email) {
 return @(name.boolValue && email.boolValue);
 }]; }];
 

  103. 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];
 

  104. 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];
 

  105. 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];
 

  106. 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];
 

  107. Your name Your email address Sign Up validate Newsletter

  108. Newsletter Your name Your email address Sign Up Brooklyn 4:00

    PM 100%
  109. Newsletter Your email address Sign Up robb Brooklyn 4:00 PM

    100%
  110. Newsletter Sign Up robb robb@robb.is Brooklyn 4:00 PM 100%

  111. Newsletter Sign Up robb Sign Up robb@robb.is Brooklyn 4:00 PM

    100%
  112. Recap

  113. + Declarative code Recap

  114. + Declarative code + Easy handling of asynchronous tasks Recap

  115. + Declarative code + Easy handling of asynchronous tasks +

    Great Composition Recap
  116. + Declarative code + Easy handling of asynchronous tasks +

    Great Composition − Conceptual overhead Recap
  117. + Declarative code + Easy handling of asynchronous tasks +

    Great Composition − Conceptual overhead − Debugging can get tricky Recap
  118. + Declarative code + Easy handling of asynchronous tasks +

    Great Composition − Conceptual overhead − Debugging can get tricky Recap (dtrace support)
  119. Qs?

  120. Thanks!

  121. iPhone 5 Sketch Template
 courtesy of
 Denis Rojčyk ! Cloud

    icon
 curtesy of Dmitry Baranovskiy, from The Noun Project