Slide 1

Slide 1 text

Scaling iOS Development Using

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Agenda 1. Simplicity is the goal of software development 2. Statefulness is an enemy of simplicity 3. FRP conceals state rather than eliminate it

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

Simplicity is the goal of programming.

Slide 10

Slide 10 text

Obvious? Maybe.

Slide 11

Slide 11 text

What is simple? • Focused • Uncombined • Has one role, one purpose, one concept, etc… • Simple is not necessarily easy

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

let numbers = [1, 5, 4, -1, 7] let sum = numbers.reduce(0, combine: +) print(sum)

Slide 14

Slide 14 text

Is that simpler? Is that easier?

Slide 15

Slide 15 text

Complexity: incidental vs. essential.

Slide 16

Slide 16 text

Minimize incidental complexity, maximize simplicity.

Slide 17

Slide 17 text

Easy doesn’t scale.

Slide 18

Slide 18 text

Choose : Easy to write? Or simple to read?

Slide 19

Slide 19 text

–Harold Abelson, SICP “Programs must be written for people to read, and only incidentally for machines to execute.”

Slide 20

Slide 20 text

Example • Form validation • Submit button is disabled until data entry is valid • Also disabled while the form is being submitted

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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) } }

Slide 24

Slide 24 text

let handler = FormHandler(callback: { enabled in self.submitButton.enabled = enabled } formDidChange(form: Form) { handler.update(form) } submitForm(form: Form) { handler.submit(form) }

Slide 25

Slide 25 text

State leads to incidental complexity.

Slide 26

Slide 26 text

Make things simple by avoiding state.

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

What is state, even!?

Slide 29

Slide 29 text

Current values of any accessible variables. (-ish)

Slide 30

Slide 30 text

# of bools 1 2 3 4 5 possible states 2 4 8 16 32

Slide 31

Slide 31 text

State changes over time.

Slide 32

Slide 32 text

Managing state is incidental complexity.

Slide 33

Slide 33 text

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 } }

Slide 34

Slide 34 text

OMG! All those classes!

Slide 35

Slide 35 text

Relaaaaaaax.

Slide 36

Slide 36 text

private func invokeCallback(form: Form) { callback( submitter.isSubmitting == false && validator.validate(form) == true ) }

Slide 37

Slide 37 text

func validate(form: Form) { invokeCallback(form) }

Slide 38

Slide 38 text

func submit(form: Form) { submitter.submit(form, startedSubmission: { self.invokeCallback(form) }, completedSubmission: { self.resetter.resetIfNecessary(form) self.invokeCallback(form) }) }

Slide 39

Slide 39 text

func submit(form: Form) { submitter.submit(form, startedSubmission: { self.invokeCallback(form) }, completedSubmission: { self.resetter.resetIfNecessary(form) self.invokeCallback(form) }) }

Slide 40

Slide 40 text

func submit(form: Form) { submitter.submit(form, startedSubmission: { self.invokeCallback(form) }, completedSubmission: { self.resetter.resetIfNecessary(form) self.invokeCallback(form) }) }

Slide 41

Slide 41 text

How much state is that?

Slide 42

Slide 42 text

None.

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

State contained within the submitter.

Slide 45

Slide 45 text

This is not pedagogical.

Slide 46

Slide 46 text

I would ship this.

Slide 47

Slide 47 text

(After writing unit tests.)

Slide 48

Slide 48 text

Simpler view controller.

Slide 49

Slide 49 text

Isolated state.

Slide 50

Slide 50 text

So. State doesn’t scale.

Slide 51

Slide 51 text

Why?

Slide 52

Slide 52 text

Because… Brains don’t scale.

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

So state is bad…

Slide 55

Slide 55 text

Let’s remove it!

Slide 56

Slide 56 text

Not so fast.

Slide 57

Slide 57 text

Abstraction.

Slide 58

Slide 58 text

–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.”

Slide 59

Slide 59 text

var loginStatus = UserStatus.NotLoggedIn ... loginStatus = .LoggedIn

Slide 60

Slide 60 text

let loginStatus = Variable(UserStatus.NotLoggedIn) ... loginStatus.value = .LoggedIn

Slide 61

Slide 61 text

Okay, but why?

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Conceals state.

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

No access to state.

Slide 70

Slide 70 text

Reacts to state changes.

Slide 71

Slide 71 text

Functional.

Slide 72

Slide 72 text

Why use a Variable?

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

class LoginNetworkModel { func login(username: String, password: String) -> Observable { return network .authUsername(username, password: password) .map { $0.statusCode } .map { statusCode -> UserStatus in statusCode == 200 ? .LoggedIn : .NotLoggedIn } } }

Slide 75

Slide 75 text

Decreased incidental complexity.

Slide 76

Slide 76 text

Network access in an Observable.

Slide 77

Slide 77 text

loginNetworkModel .login("ashfurrow", password: ...) .subscribeNext { [weak self] result in switch result { case .NotLoggedIn: self?.failedToLogin() case .LoggedIn: self?.welcomeUser() }
 }

Slide 78

Slide 78 text

Seems like magic!

Slide 79

Slide 79 text

Sure does.

Slide 80

Slide 80 text

Subscribing “starts” the Observable. (-ish)

Slide 81

Slide 81 text

Subscribing returns a Disposable.

Slide 82

Slide 82 text

Tie disposal to object deallocation.

Slide 83

Slide 83 text

class LoginViewController: UIViewController { let disposeBag = DisposeBag() ... loginNetworkModel .login("ashfurrow", password: ...) .subscribeNext { ... } .addDisposableTo(self.disposeBag) ...

Slide 84

Slide 84 text

OK, cool! What else?

Slide 85

Slide 85 text

Requirements Change! • View controller needs access to full user info • Let’s modify our network model

Slide 86

Slide 86 text

class LoginNetworkModel { func login(username: String, password: String) -> Observable { return network .authUsername(username, password: password) .filterSuccessfulStatusCodes() .mapToJSON() .mapToObject(User) } }

Slide 87

Slide 87 text

loginNetworkModel .login("ashfurrow", password: …) .onError { [weak self] error in self?.handleFailure(error) } .subscribeNext { ... } .addDisposableTo(self.disposeBag)

Slide 88

Slide 88 text

That’s really neat!

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

Benefits • Token not validated until needed • Inject behaviour into existing networking pipeline • Transparently • Example: github.com/Moya/Moya

Slide 94

Slide 94 text

Are Observables monads?

Slide 95

Slide 95 text

We don’t use the ‘m’ word here.

Slide 96

Slide 96 text

There’s a lot more.

Slide 97

Slide 97 text

Remember the FormHandler?

Slide 98

Slide 98 text

extension FormHandler { var enabled: Observable { 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 } } }

Slide 99

Slide 99 text

extension FormHandler { var enabled: Observable { 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 } } }

Slide 100

Slide 100 text

extension FormHandler { var enabled: Observable { 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 } } }

Slide 101

Slide 101 text

extension FormHandler { var enabled: Observable { 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 } } }

Slide 102

Slide 102 text

That’s testable!

Slide 103

Slide 103 text

class LoginViewController { ... formHandler .enabled .bindTo(submitButton.rx_enabled) .addDisposableTo(disposeBag) ... }

Slide 104

Slide 104 text

That’s testable, too!

Slide 105

Slide 105 text

github.com/artsy/eidolon

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

So is FRP the future?

Slide 108

Slide 108 text

For now.