$30 off During Our Annual Pro Sale. View Details »

RAC3, A Real World Use Case (aka ReactiveChess)

RAC3, A Real World Use Case (aka ReactiveChess)

Overview of how WatchChess was implemented using ReactiveCocoa 3.0, sharing a few tricks that I learned during its development.

Javier Soto

June 11, 2015
Tweet

More Decks by Javier Soto

Other Decks in Programming

Transcript

  1. RAC3, A Real World Use Case
    aka
    Reac%veChess
    Reac%veChess - Javier Soto, RACDC 2015 1

    View Slide

  2. Javier Soto (@Javi)
    iOS at Twi)er
    Reac%veChess - Javier Soto, RACDC 2015 2

    View Slide

  3. Predic'on:
    All of the code in these slides won't compile on
    the latest Xcode by the 6me you're reading this.
    Reac%veChess - Javier Soto, RACDC 2015 3

    View Slide

  4. Reac%veChess - Javier Soto, RACDC 2015 4

    View Slide

  5. WatchChess.app
    Reac%veChess - Javier Soto, RACDC 2015 5

    View Slide

  6. WatchChess.app
    • Reac&veCocoa 3.0
    • Argo
    History
    • First prototype: Swi/ 1.2 with Xcode 6.3 Beta 1
    • v1.0: Swi/ 1.1, RAC 3.0 pre-alpha
    • v1.1: Swi/ 1.2, RAC 3.0-beta.1
    • v1.2: RAC 3.0-beta.6
    Reac%veChess - Javier Soto, RACDC 2015 6

    View Slide

  7. Big Takeaways
    • Typed Signals
    • Less debugging required
    • Conciseness
    • Clearer seman8cs
    Reac%veChess - Javier Soto, RACDC 2015 7

    View Slide

  8. Conciseness
    [[[[client
    logInUser]
    flattenMap:^(User *user) {
    return [client loadCachedMessages:user];
    }]
    flattenMap:^(NSArray *messages) {
    return [client fetchMessagesAfter:messages.lastObject];
    }]
    subscribeNext:^(NSArray *newMessages) {
    NSLog(@"New messages: %@", newMessages);
    } completed:^{
    NSLog(@"Fetched all messages.");
    }];
    Reac%veChess - Javier Soto, RACDC 2015 8

    View Slide

  9. Conciseness
    client.loginUser()
    |> flatMap(.Latest) { client.loadCachedMessages($0) }
    |> flatMap(.Concat) { client.fetchMessagesAfter($0.last) }
    |> start(next: { println("New messages: \($0)") },
    completed: { println("Fetched all messages.") })
    Reac%veChess - Javier Soto, RACDC 2015 9

    View Slide

  10. Biggest sources of frustra.on
    • Compiler crashes
    • Type errors
    Reac%veChess - Javier Soto, RACDC 2015 10

    View Slide

  11. Figure out type errors in RAC 3 with
    this one weird trick
    Reac%veChess - Javier Soto, RACDC 2015 11

    View Slide

  12. Type Errors
    WAT
    Reac%veChess - Javier Soto, RACDC 2015 12

    View Slide

  13. Type Errors
    Extract intermediate results into separate values
    Reac%veChess - Javier Soto, RACDC 2015 13

    View Slide

  14. Type Errors
    Inspect the types (⌥ + click)
    Reac%veChess - Javier Soto, RACDC 2015 14

    View Slide

  15. Type Errors
    Inspect the types (⌥ + click)
    Reac%veChess - Javier Soto, RACDC 2015 15

    View Slide

  16. Type Errors
    Check RAC's func.on signatures (⌘ + click)
    Reac%veChess - Javier Soto, RACDC 2015 16

    View Slide

  17. Type Errors
    Check if your types match those expected by RAC
    Reac%veChess - Javier Soto, RACDC 2015 17

    View Slide

  18. Type Errors
    Look for a func+on that matches what you're trying to do
    Reac%veChess - Javier Soto, RACDC 2015 18

    View Slide

  19. Type Errors
    If it compiles, it works.
    Reac%veChess - Javier Soto, RACDC 2015 19

    View Slide

  20. ChessAPI.swift
    Reac%veChess - Javier Soto, RACDC 2015 20

    View Slide

  21. ChessAPI.swift
    enum APIError: ErrorType {
    case NetworkError(NSError)
    case JSONParsingError(NSError)
    case InvalidJSONStructure(reason: String)
    }
    Reac%veChess - Javier Soto, RACDC 2015 21

    View Slide

  22. ChessAPI.swift
    import ReactiveCocoa
    import Argo
    func tournaments() -> SignalProducer<[Tournament], APIError> {
    return self.JSONWithPath("/tournaments")
    |> attemptMap(extractJSONKey("tournaments"))
    |> attemptMap(extractJSONObjects)
    |> reverse
    }
    Reac%veChess - Javier Soto, RACDC 2015 22

    View Slide

  23. ChessAPI.swift
    func JSONWithPath(path: String) -> SignalProducer {
    let request = APIRequestWithPath(path)
    let URL = request.URL
    return self.urlSession.rac_dataWithRequest(request)
    |> log(started: "Started: \(URL)",
    completed: "Finished: \(URL)")
    |> mapError { APIError.NetworkError($0) }
    |> map { $0.0 }
    |> attemptMap(parseJSON)
    }
    Reac%veChess - Javier Soto, RACDC 2015 23

    View Slide

  24. ChessAPI.swift
    func parseJSON(data: NSData) -> Result {
    var error: NSError?
    if let json = NSJSONSerialization(data, error: &error) {
    return success(JSON.parse(json))
    }
    else {
    return failure(APIError.JSONParsingError(error!))
    }
    }
    Reac%veChess - Javier Soto, RACDC 2015 24

    View Slide

  25. Custom RAC Operators
    Reac%veChess - Javier Soto, RACDC 2015 25

    View Slide

  26. Custom RAC Operators
    return self.urlSession.rac_dataWithRequest(request)
    |> log(started: "Started: \(URL)",
    completed: "Finished: \(URL)")
    Reac%veChess - Javier Soto, RACDC 2015 26

    View Slide

  27. Custom RAC Operators
    log():
    func log(started: String = "",
    next: String = "",
    completed: String = "")
    (producer: SignalProducer)
    -> SignalProducer {
    return producer
    |> on(started: { println(started) },
    next: { println(next + " \($0)") },
    completed: { println(completed) })
    }
    Reac%veChess - Javier Soto, RACDC 2015 27

    View Slide

  28. Custom RAC Operators
    log():
    func log(started: String = "",
    next: String = "",
    completed: String = "")
    (producer: SignalProducer)
    -> SignalProducer
    (String, String, String) -> SignalProducer -> SignalProducer
    Reac%veChess - Javier Soto, RACDC 2015 28

    View Slide

  29. Custom RAC Operators
    log():
    (String, String, String) -> SignalProducer -> SignalProducer
    log("someMessage"): SignalProducer -> SignalProducer
    |>:
    (SignalProducer, (SignalProducer -> SignalProducer)) -> SignalProducer
    // Infix order would be...
    // SignalProducer |> (SignalProducer -> SignalProducer) -> SignalProducer
    Reac%veChess - Javier Soto, RACDC 2015 29

    View Slide

  30. Custom RAC Operators
    return self.urlSession.rac_dataWithRequest(request)
    |> log(started: "Started: \(URL)",
    completed: "Finished: \(URL)")
    Reac%veChess - Javier Soto, RACDC 2015 30

    View Slide

  31. WatchKit Controllers
    import WatchKit
    class TournamentsInterfaceController: WKInterfaceController {
    private let tournaments = MutableProperty<[Tournament]>([])
    override init() {
    super.init()
    self.tournaments.producer |> skip(1)
    |> skipRepeatedArrays
    |> start(next: { [unowned self] tournaments in
    self.updateUIWithTournaments(tournaments)
    })
    }
    override func willActivate() {
    self.tournaments <~ self.chessAPI.requestTournaments()
    |> printAndFilterErrors("Error requesting tournaments")
    |> observeOn(UIScheduler())
    }
    }
    Reac%veChess - Javier Soto, RACDC 2015 31

    View Slide

  32. WatchKit Controllers
    self.tournaments <~ self.chessAPI.requestTournaments()
    // To bind to a property, the signal can't send errors.
    |> printAndFilterErrors("Error requesting tournaments")
    }
    Reac%veChess - Javier Soto, RACDC 2015 32

    View Slide

  33. printAndFilterErrors()
    func printAndFilterErrors(signal: Signal)
    -> Signal {
    return signal
    |> on(error: { println("\(message): \($0)") })
    |> catch { SignalProducer.empty }
    }
    Reac%veChess - Javier Soto, RACDC 2015 33

    View Slide

  34. WatchKit Controllers
    self.tournaments.producer
    // Easy optimization to minimize Bluetooth round-trips.
    |> skipRepeatedArrays
    |> start(next: { [unowned self] tournaments in
    self.updateUIWithTournaments(tournaments)
    })
    Reac%veChess - Javier Soto, RACDC 2015 34

    View Slide

  35. skipRepeatedArrays()
    public func skipRepeatedArrays(signal: Signal<[T], E>)
    -> Signal<[T], E> {
    return signal |> skipRepeats(==)
    }
    Reac%veChess - Javier Soto, RACDC 2015 35

    View Slide

  36. THANKS
    • Jus%n Spahr-Summers (@jspahrsummers)
    • Nacho Soto (@NachoSoto)
    • You
    Reac%veChess - Javier Soto, RACDC 2015 36

    View Slide

  37. Ques%ons?
    Reac%veChess - Javier Soto, RACDC 2015 37

    View Slide