Functional Reactive Programming on iOS

Functional Reactive Programming on iOS

Most iOS applications make heavy use of mutable state, which increases code complexity and results in software that is expensive to maintain. Functional Reactive Programming provides us with a toolset that reduces our dependency on mutable state.

This talk will discuss the philosophy of Functional Reactive Programming and show how ReactiveCocoa can be used to write maintainable and testable UI and Networking code. It will show that FRP offers some very pragmatic approaches to fix common issues in your codebase.

De23af005c790b22f8ce4d201e6ca027?s=128

Benjamin Encz

April 01, 2015
Tweet

Transcript

  1. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Functional Reactive

    Programming on iOS
  2. @benjaminencz Functional Reactive Programming on iOS | NSMeetup

  3. @benjaminencz Functional Reactive Programming on iOS | NSMeetup No entry

    Default Loading 3 High level states
  4. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Button tapped

    Network error callback Textfield text changed disable disable enable enable …; if text enable else disable textfield textfield button button button button
  5. @benjaminencz Functional Reactive Programming on iOS | NSMeetup State propagation

    is handled manually by mutating variables
  6. @benjaminencz Functional Reactive Programming on iOS | NSMeetup • State

    handling code is dispersed • Code is hard to read • Code is hard to maintain Problem
  7. @benjaminencz Functional Reactive Programming on iOS | NSMeetup No entry

    Default Loading 3 High level states 9 possible invalid states!
  8. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Manual state

    management is error prone
  9. @benjaminencz Functional Reactive Programming on iOS | NSMeetup What is

    functional reactive programming?
  10. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Imperative vs.

    Declarative
  11. @benjaminencz Functional Reactive Programming on iOS | NSMeetup A B

    C 20 10 ? 1. Perform the following steps whenever A or B changes 2. Add 50 to value of A 3. Subtract 10 from value of B 4. Add the results from 2.) and 3.) 5. Write result from 4.) into C Imperative
  12. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Declarative A

    B C 20 10 ? C = (A+50) + (B-10)
  13. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Imperative

  14. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Button tapped

    Network error callback Textfield text changed disable disable enable enable …; if text enable else disable textfield textfield textfield textfield button button
  15. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Declarative

  16. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Stateless Function

    Event emitters Functional Reactive Textfield text changed disable textfield button enable textfield disable enable button decide decide Button tapped Network error callback Button tapped Network error callback
  17. @benjaminencz Functional Reactive Programming on iOS | NSMeetup State is

    derived from a defined set of inputs
  18. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Intro to

    Reactive Cocoa 2.x
  19. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Signals send

    values over time
  20. @benjaminencz Functional Reactive Programming on iOS | NSMeetup • Callbacks

    • Delegate methods • KVO / Property overriding Signals } Values over time
  21. @benjaminencz Functional Reactive Programming on iOS | NSMeetup RACSignal sendNext:

    send Completed: send Error: Val 1 Val 2 Subscriber Subscriber Subscriber
  22. @benjaminencz Functional Reactive Programming on iOS | NSMeetup We can

    bind Signals OR subscribe to Signals
  23. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Textfield text

    changed user.email “h” “he” “hel” Bind RAC(self.user, username) = self.usernameTextfield.rac_textSignal
  24. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Subscribe [self.usernameTextfield.rac_textSignal

    subscribeNext:^(NSString *t) { NSLog(@“New value: %@", x); }]; Textfield text changed “h” “he” “hel” side effects; imperative code;
  25. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Prefer binding

    over explicit subscription
  26. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Model ->

    View Binding with Reactive Cocoa
  27. @benjaminencz Functional Reactive Programming on iOS | NSMeetup - (void)awakeFromNib

    { RAC(self, avatarImageView.image) = RACObserve(self, model.avatar); RAC(self, nameLabel.text) = RACObserve(self, model.name); // more binding code } View updates whenever
 model or model properties change
  28. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Model View

  29. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Model View

    ?
  30. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Textfield text

    changed button. enabled map “nsmeetup” YES Textfield text changed button. enabled map “” NO Signal Operators
  31. @benjaminencz Functional Reactive Programming on iOS | NSMeetup RACCommand Execution

    Signals Subscriber Signal 1 Signal 2 Subscriber Val 1 Val 1
  32. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Model View

    ?
  33. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Model ViewModel

    View Stores View state, communicates
 with model Stores model state, provides business logic Bindings
  34. @benjaminencz Functional Reactive Programming on iOS | NSMeetup

  35. @benjaminencz Functional Reactive Programming on iOS | NSMeetup PersonAddingView* PersonAddingViewModel

    usernameSearchText addButtonCommand
 addButtonEnabledSignal usernameTextfield.text addButton.rac_command *some variables have been renamed for brevity
  36. @benjaminencz Functional Reactive Programming on iOS | NSMeetup self.addTwitterButton.rac_command =

    self.viewModel.addTwitterButtonCommand; RAC(self.usernameTextfield, enabled) = self.viewModel.textFieldEnabledSignal; PersonAddingView Initialization
  37. @benjaminencz Functional Reactive Programming on iOS | NSMeetup self.addButtonEnabledSignal =

    [RACObserve(self, usernameSearchText) map:^id(NSString *searchText) { if (!searchText || [searchText isEqualToString:@""]) { return @(NO); } else { return @(YES); } }]; PersonAddingViewModel Enabling / Disabling the add button
  38. @benjaminencz Functional Reactive Programming on iOS | NSMeetup PersonAddingViewModel @""

    NO
  39. @benjaminencz Functional Reactive Programming on iOS | NSMeetup PersonAddingViewModel @"nsmeetup"

    YES
  40. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Networking with

    Reactive Cocoa
  41. @benjaminencz Functional Reactive Programming on iOS | NSMeetup

  42. @benjaminencz Functional Reactive Programming on iOS | NSMeetup PersonAddingView PersonDetailView

    PersonContainerView {
  43. @benjaminencz Functional Reactive Programming on iOS | NSMeetup self.addTwitterButtonCommand =

    [[RACCommand alloc] initWithEnabled:self.addButtonEnabledSignal signalBlock:^RACSignal *(id input) { RACSignal *signal = [self.twitterClient infoForUsername:self.usernameSearchText]; return signal; } ]; PersonAddingViewModel Kicking off the network request
  44. @benjaminencz Functional Reactive Programming on iOS | NSMeetup self.addTwitterButtonCommand =

    [[RACCommand alloc] initWithEnabled:self.addButtonEnabledSignal signalBlock:^RACSignal *(id input) { RACSignal *signal = [self.twitterClient infoForUsername:self.usernameSearchText]; return signal; } ]; PersonAddingViewModel Kicking off the network request
  45. @benjaminencz Functional Reactive Programming on iOS | NSMeetup self.addTwitterButtonCommand =

    [[RACCommand alloc] initWithEnabled:self.addButtonEnabledSignal signalBlock:^RACSignal *(id input) { RACSignal *signal = [self.twitterClient infoForUsername:self.usernameSearchText]; return signal; } ]; PersonAddingViewModel Kicking off the network request We are doing exactly one thing. We don’t need
 to handle callbacks here, just start the request!
  46. @benjaminencz Functional Reactive Programming on iOS | NSMeetup // subscribe

    to twitter network request RACSignal *twitterFetchSignal = [RACObserve(self, personAddingViewModel) flattenMap:^RACStream *(id value) { return [self.personAddingViewModel. addTwitterButtonCommand.executionSignals concat]; }]; RACSignal *UIStateSignal = [[twitterFetchSignal map:^id(id value) { return @(DetailViewState); }] startWith:@(AddingViewState)]; RAC(self, UIState) = UIStateSignal;
 RAC(self, person) = twitterFetchSignal; PersonContainerViewModel Changing the UIState upon completed request
  47. @benjaminencz Functional Reactive Programming on iOS | NSMeetup // subscribe

    to twitter network request RACSignal *twitterFetchSignal = [RACObserve(self, personAddingViewModel) flattenMap:^RACStream *(PersonAddingViewModel *addingViewModel) { return [addingViewModel.addTwitterButtonCommand.executionSignals concat]; }]; RACSignal *UIStateSignal = [[twitterFetchSignal map:^id(id value) { return @(DetailViewState); }] startWith:@(AddingViewState)]; RAC(self, UIState) = UIStateSignal; RAC(self, person) = twitterFetchSignal; PersonContainerViewModel Changing the UIState upon completed request
  48. @benjaminencz Functional Reactive Programming on iOS | NSMeetup // subscribe

    to twitter network request RACSignal *twitterFetchSignal = [RACObserve(self, personAddingViewModel) flattenMap:^RACStream *(id value) { return [self.personAddingViewModel. addTwitterButtonCommand.executionSignals concat]; }]; RACSignal *UIStateSignal = [[twitterFetchSignal map:^id(id value) { return @(DetailViewState); }] startWith:@(AddingViewState)]; RAC(self, UIState) = UIStateSignal; RAC(self, person) = twitterFetchSignal; PersonContainerViewModel Changing the UIState upon completed request
  49. @benjaminencz Functional Reactive Programming on iOS | NSMeetup

  50. @benjaminencz Functional Reactive Programming on iOS | NSMeetup - (RACSignal

    *)infoForUsername:(NSString *)username { ... return [[[[[self _login] deliverOn:bgScheduler] flattenMap:^RACStream *(STTwitterAPI *client) { return [self client:client fetchUserInfo:username]; }] flattenMap:^RACStream *(NSDictionary *userInfo) { return [[self imageFromURLString:userInfo[@"userInfo"]] combineLatestWith:[RACSignal return:userInfo]]; }] flattenMap:^RACStream *(RACTuple *personInfoTupel) { return [RACSignal return:[self _personFromUserInfo:personInfoTupel]]; }]; } Twitter API request Chaining network operations
  51. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Model ViewModel

    View ✓
  52. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Testing with

    Reactive Cocoa 2.x
  53. @benjaminencz Functional Reactive Programming on iOS | NSMeetup it(@"calls the

    Twitter API when add button is tapped", ^{ id twitterClient = [TwitterClient new]; id twitterMock = OCMPartialMock(twitterClient); OCMStub([twitterMock infoForUsername:@“username"]) .andReturn([RACSignal return:@(YES)]); viewModel = [[PersonAddingViewModel alloc] initWithTwitterClient:twitterMock]; viewModel.usernameSearchText = @"username"; [viewModel.addTwitterButtonCommand execute:nil]; OCMVerify([twitterMock infoForUsername:@"username"]); }); Testing UI without UIKit
  54. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Summary

  55. @benjaminencz Functional Reactive Programming on iOS | NSMeetup Textfield text

    changed disable textfield button enable textfield disable enable button decide decide Button tapped Network error callback Button tapped Network error callback
  56. @benjaminencz Functional Reactive Programming on iOS | NSMeetup • RAC

    introduces a vastly different programming model that can be harder to debug • RAC provides tools for writing simpler declarative code that embraces derived state

  57. @benjaminencz Functional Reactive Programming on iOS | NSMeetup • MVVM

    plays nicely with bindings, eliminates controller complexity
 • MVVM makes it easier to write testable code
  58. @benjaminencz Functional Reactive Programming on iOS | NSMeetup “[…] our

    intellectual powers are rather geared to master static relations and […] our powers to visualize processes evolving in time are relatively poorly developed.“  E.W. Dijkstra (And a ton of talks & blog posts that quoted him in the context of Reactive Programming)
  59. @benjaminencz Functional Reactive Programming on iOS | NSMeetup • Code:

    https://github.com/Ben-G/PeopleCRM • Further Resources: • http://www.sprynthesis.com/2014/06/15/why- reactivecocoa/ • Functional Reactive Programming on iOS, Ash Furrow (https://leanpub.com/iosfrp/) Thank you! Thanks to Ash Furrow, Morgan Chen, Gerald Monaco, Florian Krueger and Dave Lee for input and feedback on this talk!