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

ReactiveCocoa NSSpain

B00d4ed5d1e6cb9fc840167820247515?s=47 Robert Böhnke
September 18, 2013

ReactiveCocoa NSSpain

My talk for NSSpain 2013 on ReactiveCocoa

B00d4ed5d1e6cb9fc840167820247515?s=128

Robert Böhnke

September 18, 2013
Tweet

Transcript

  1. robb robb@robb.is ceterum_censeo

  2. speakerdeck.com/robb/ reactivecocoa-nsspain

  3. Let's talk about ReactiveCocoa

  4. Let's talk about ReactiveCocoa state

  5. evil √

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

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

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

  9. Start Okay Fail power-cycle power-cycle

  10. state

  11. state

  12. duh! " " you say

  13. e.g.

  14. @property (readwrite) BOOL walks; @property (readwrite) BOOL quaks; @property (readonly)

    BOOL duck;
  15. duck = walks ∧ quaks

  16. - (void)setWalks:(BOOL)walks { _walks = walks; self.duck = walks &&

    self.quacks; }
  17. - (void)setWalks:(BOOL)walks { _walks = walks; self.duck = walks &&

    self.quacks; } - (void)setQuacks:(BOOL)quacks { _quacks = quacks; self.duck = quacks && self.walks; }
  18. - (void)setWalks:(BOOL)walks { _walks = walks; self.duck = walks &&

    self.quacks; } - (void)setQuacks:(BOOL)quacks { _quacks = quacks; self.duck = quacks && self.walks; }
  19. - (BOOL)duck { return self.walks && self.quacks; }

  20. - (NSSet *)keyPathsForValuesAffectingDuck { return [NSSet setWithArray:@[ @"walks", @"quacks" ]];

    } - (BOOL)duck { return self.walks && self.quacks; }
  21. - (BOOL)duck { return self.walks && self.quacks; } // duck

    = walks ∧ quaks
  22. ReactiveCocoa

  23. Functional Reactive Programming

  24. Signals instead of variables

  25. Pipes, not boxes

  26. wtf? " " you may be thinking

  27. e.g.

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

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

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

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

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

    reduce:^(NSNumber *walks, NSNumber *quacks) { return @(walks.boolValue && quacks.boolValue); }]:
  33. // and it even does KVO! RAC(self, duck) = [RACSignal

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

  35. & walks talks duck

  36. & walks talks duck

  37. & walks talks duck

  38. & walks talks duck

  39. RACSignal

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

  41. <RACSubscriber>

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

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

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

  45. so what?

  46. Powerful toolset

  47. map

  48. filter

  49. fold

  50. write declarative code

  51. e.g.

  52. Your name Your email address Sign Up Newsletter

  53. Newsletter Your name Your email address Sign Up

  54. Newsletter Your email address Sign Up robb

  55. Newsletter Sign Up robb robb

  56. Newsletter Sign Up robb @robb.is Sign Up robb

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

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

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

  62. 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); }]:
  63. 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); }]:
  64. 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); }]:
  65. 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); }]:
  66. Your name Your email address Sign Up validate Newsletter

  67. Newsletter Your name Your email address Sign Up

  68. Newsletter Sign Up robb

  69. Newsletter Sign Up robb robb@robb.is Sign Up

  70. Let's talk about

  71. Let's talk about Asynchrony

  72. [client logInWithSuccess:^{ [client loadMeUserWithSuccess:^(SPUser *me) { [client loadFilesForUser:me withSuccess:^(NSArray *files)

    { 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?!
  73. Let's fix this

  74. 1. HTTP Client

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

  76. [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]; }];
  77. [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]; }];
  78. - (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];
  79. - (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];
  80. - (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];
  81. [self enqueueHTTPRequestOperation:operation]; return [RACDisposable disposableWithBlock:^{ [operation cancel]; }]; }]; 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]; }];
  82. [self enqueueHTTPRequestOperation:operation]; return [RACDisposable disposableWithBlock:^{ [operation cancel]; }]; }]; 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]; }];
  83. 2. API interaction

  84. - (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]; } }]; }
  85. - (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]; } }]; }
  86. - (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]; } }]; }
  87. - (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]; } }]; }
  88. - (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!
  89. - (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]; } }]; }
  90. - (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]; } }]; }
  91. 3. Putting it together

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

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

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

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

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

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

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

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

  100. UI+IO

  101. Your name Your email address Sign Up Newsletter

  102. Your name Your email address Sign Up validate Newsletter

  103. Your name Your email address Sign Up validate Newsletter

  104. , why me? oh boy

  105. 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:@[ ]
  106. 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:@[ ]
  107. validName, validEmail reduce:^(NSString *name, NSString *email) { NSRange at =

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

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

    validName, validEmail map:^(NSString *name) { return @(name.length > 0); }]; RAC(self.signUpButton, enabled) = [RACSignal combineLatest:@[ ]
  110. RACSignal *validName = [self.name.rac_textSignal map:^(NSString *name) { return @(name.length >

    0); }]; reduce:^(NSNumber *name, NSNumber *email) { validName, validEmail RAC(self.signUpButton, enabled) = [RACSignal combineLatest:@[ ] RACSignal *validEmail = [[self.email.rac_textSignal map:^(NSString *email) { return [[client validateEmail:email] startWith:@NO]; }] switchToLatest];
  111. RACSignal *validName = [self.name.rac_textSignal map:^(NSString *name) { return @(name.length >

    0); }]; RACSignal *validEmail = [[self.email.rac_textSignal map:^(NSString *email) { return [[client validateEmail:email] startWith:@NO]; }] switchToLatest];
  112. 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 @(name.length > 0); }];
  113. 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 @(name.length > 0); }];
  114. Your name Your email address Sign Up validate Newsletter

  115. Newsletter Your name Your email address Sign Up

  116. Newsletter Sign Up robb

  117. Newsletter Sign Up robb robb@robb.is

  118. Newsletter Sign Up robb robb@robb.is Sign Up

  119. Recap

  120. Recap + Declarative code

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

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

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

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

    + Great Composition − Conceptual overhead − Debugging can get tricky
  125. Recap (dtrace support coming soon) + Declarative code + Easy

    handling of asynchronous tasks + Great Composition − Conceptual overhead − Debugging can get tricky
  126. Qs?

  127. Thanks!

  128. iPhone 5 Setch Template courtesy of Denis Rojčyk Cloud icon

    curtesy of Dmitry Baranovskiy, from The Noun Project