Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Scaling iOS Development with FRP

Ash Furrow
February 23, 2016

Scaling iOS Development with FRP

Ash Furrow

February 23, 2016
Tweet

More Decks by Ash Furrow

Other Decks in Programming

Transcript

  1. BNE ADD1 BRA NONE1 ADD1 INY ;increment our counter of

    1's NONE1 LDAA TEMP ;reload accumulator A LSL MASK ;Shift the mask's 1 bit left BNE LOOP1 ;If we haven't finished our loop, branch LDAA #$01 ;load new mask into A STAA MASK ;store the reset mask into MASK TSX ;pull of return address and store in X PULA ;pull off A STAA TEMP ;store the value into temp TXS ;push return address back onto the stack LOOP2 LDAA TEMP ;Load A into TEMP ANDA MASK ;logical AND MASK with A BNE ADD2 ;add one if we need to BRA NONE2 ADD2 INY ;increment our counter of 1's NONE2 LDAA TEMP LSL MASK ;shift our mask left by one BNE LOOP2 ;loop back until we've exhausted positions
  2. Agenda 1. Simplicity is the goal of software development 2.

    Statefulness is an enemy of simplicity 3. FRP conceals state rather than eliminate it
  3. What is simple? • Focused • Uncombined • Has one

    role, one purpose, one concept, etc… • Simple is not necessarily easy
  4. let numbers = [1, 5, 4, -1, 7] var sum

    = 0 for var i = 0; i < numbers.count; i++ { sum += numbers[i] } print(sum)
  5. let numbers = [1, 5, 4, -1, 7] let sum

    = numbers.reduce(0, combine: +) print(sum)
  6. –Harold Abelson, SICP “Programs must be written for people to

    read, and only incidentally for machines to execute.”
  7. Example • Form validation • Submit button is disabled until

    data entry is valid • Also disabled while the form is being submitted
  8. formDidChange(form: Form) { submitButton.enabled = validate(form) } submitForm(form: Form) {

    submitButton.enabled = false network.submitForm(form) { submitButton.enabled = true } }
  9. formDidChange(form: Form) { submitButton.enabled = validate(form) } submitForm(form: Form) {

    submitButton.enabled = false network.submitForm(form) { submitButton.enabled = validate(form) } }
  10. formDidChange(form: Form) { submitButton.enabled = validate(form) } submitForm(form: Form) {

    submitButton.enabled = false network.submitForm(form) { // Wait, do we reset the form first? Maybe. submitButton.enabled = validate(form) } }
  11. let handler = FormHandler(callback: { enabled in self.submitButton.enabled = enabled

    } formDidChange(form: Form) { handler.update(form) } submitForm(form: Form) { handler.submit(form) }
  12. class FormHandler { let callback: Bool -> Void private let

    submitter = FormSubmitter() private let validator = FormValidator() private let resetter = FormResetter() init(callback: Bool -> Void) { self.callback = callback } }
  13. struct FormHandler { let callback: Bool -> Void private let

    submitter = FormSubmitter() private let validator = FormValidator() private let resetter = FormResetter() ...
  14. –Edsger Dijkstra “The purpose of abstraction is not to be

    vague, but to create a new semantic level in which one can be absolutely precise.”
  15. class LoginNetworkModel { private let _loginStatus = Variable(UserStatus.NotLoggedIn) var loginStatus:

    Observable<UserStatus> { return _loginStatus.asObservable() } ... }
  16. class LoginNetworkModel { private let _loginStatus = Variable(UserStatus.NotLoggedIn) var loginStatus:

    Observable<UserStatus> { return _loginStatus.asObservable() } ... }
  17. class LoginNetworkModel { private let _loginStatus = Variable(UserStatus.NotLoggedIn) var loginStatus:

    Observable<UserStatus> { return _loginStatus.asObservable() } ... }
  18. loginNetworkModel .loginStatus .subscribeNext { [weak self] result in switch result

    { case .NotLoggedIn: self?.failedToLogin() case .LoggedIn: self?.welcomeUser() }
 }
  19. loginNetworkModel .loginStatus .subscribeNext { [weak self] result in switch result

    { case .NotLoggedIn: self?.failedToLogin() case .LoggedIn: self?.welcomeUser() }
 }
  20. loginNetworkModel .loginStatus .subscribeNext { [weak self] result in switch result

    { case .NotLoggedIn: self?.failedToLogin() case .LoggedIn: self?.welcomeUser() }
 }
  21. class LoginNetworkModel { func login(username: String, password: String) -> Observable<UserStatus>

    { return network .authUsername(username, password: password) .map { response in ... } } }
  22. class LoginNetworkModel { func login(username: String, password: String) -> Observable<UserStatus>

    { return network .authUsername(username, password: password) .map { $0.statusCode } .map { statusCode -> UserStatus in statusCode == 200 ? .LoggedIn : .NotLoggedIn } } }
  23. loginNetworkModel .login("ashfurrow", password: ...) .subscribeNext { [weak self] result in

    switch result { case .NotLoggedIn: self?.failedToLogin() case .LoggedIn: self?.welcomeUser() }
 }
  24. class LoginViewController: UIViewController { let disposeBag = DisposeBag() ... loginNetworkModel

    .login("ashfurrow", password: ...) .subscribeNext { ... } .addDisposableTo(self.disposeBag) ...
  25. Requirements Change! • View controller needs access to full user

    info • Let’s modify our network model
  26. class LoginNetworkModel { func login(username: String, password: String) -> Observable<User>

    { return network .authUsername(username, password: password) .filterSuccessfulStatusCodes() .mapToJSON() .mapToObject(User) } }
  27. loginNetworkModel .login("ashfurrow", password: …) .onError { [weak self] error in

    self?.handleFailure(error) } .subscribeNext { ... } .addDisposableTo(self.disposeBag)
  28. network .accessToken // Observable<AccessToken> .flatMap { token in switch token

    { case .Expired: return network.fetchToken() case .Valid: return just(token) } } .flatMap { token in return networking.performRequest(...) }
  29. network .accessToken // Observable<AccessToken> .flatMap { token in switch token

    { case .Expired: return network.fetchToken() case .Valid: return just(token) } } .flatMap { token in return networking.performRequest(...) }
  30. network .accessToken // Observable<AccessToken> .flatMap { token in switch token

    { case .Expired: return network.fetchToken() case .Valid: return just(token) } } .flatMap { token in return networking.performRequest(...) }
  31. network .accessToken // Observable<AccessToken> .flatMap { token in switch token

    { case .Expired: return network.fetchToken() case .Valid: return just(token) } } .flatMap { token in return networking.performRequest(...) }
  32. Benefits • Token not validated until needed • Inject behaviour

    into existing networking pipeline • Transparently • Example: github.com/Moya/Moya
  33. extension FormHandler { var enabled: Observable<Bool> { return [submitter.submitting, validator.valid]

    .combineLatest { values in return ( submitting: values[0], valid: values[1] ) // Create a tuple } .map { (submitting, valid) in return submitting == false && valid == true } } }
  34. extension FormHandler { var enabled: Observable<Bool> { return [submitter.submitting, validator.valid]

    .combineLatest { values in return ( submitting: values[0], valid: values[1] ) // Create a tuple } .map { (submitting, valid) in return submitting == false && valid == true } } }
  35. extension FormHandler { var enabled: Observable<Bool> { return [submitter.submitting, validator.valid]

    .combineLatest { values in return ( submitting: values[0], valid: values[1] ) // Create a tuple } .map { (submitting, valid) in return submitting == false && valid == true } } }
  36. extension FormHandler { var enabled: Observable<Bool> { return [submitter.submitting, validator.valid]

    .combineLatest { values in return ( submitting: values[0], valid: values[1] ) // Create a tuple } .map { (submitting, valid) in return submitting == false && valid == true } } }
  37. Wrap-up 1. Strive for simplicity because brains don’t scale 2.

    State increases incidental complexity 3. Rather than eliminate state, FRP abstract it away