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

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. 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
  2. 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
  3. Big Takeaways • Typed Signals • Less debugging required •

    Conciseness • Clearer seman8cs Reac%veChess - Javier Soto, RACDC 2015 7
  4. 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
  5. 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
  6. Biggest sources of frustra.on • Compiler crashes • Type errors

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

    weird trick Reac%veChess - Javier Soto, RACDC 2015 11
  8. Type Errors Check if your types match those expected by

    RAC Reac%veChess - Javier Soto, RACDC 2015 17
  9. Type Errors Look for a func+on that matches what you're

    trying to do Reac%veChess - Javier Soto, RACDC 2015 18
  10. ChessAPI.swift enum APIError: ErrorType { case NetworkError(NSError) case JSONParsingError(NSError) case

    InvalidJSONStructure(reason: String) } Reac%veChess - Javier Soto, RACDC 2015 21
  11. 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
  12. ChessAPI.swift func JSONWithPath(path: String) -> SignalProducer<JSON, APIError> { 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
  13. ChessAPI.swift func parseJSON(data: NSData) -> Result<JSON, APIError> { 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
  14. Custom RAC Operators log(): func log<T, E: ErrorType>(started: String =

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

    "", next: String = "", completed: String = "") (producer: SignalProducer<T, E>) -> SignalProducer<T, E> (String, String, String) -> SignalProducer -> SignalProducer Reac%veChess - Javier Soto, RACDC 2015 28
  16. 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
  17. 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
  18. 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
  19. printAndFilterErrors() func printAndFilterErrors<T, E: ErrorType)(message: String) (signal: Signal<T, E>) ->

    Signal<T, NoError> { return signal |> on(error: { println("\(message): \($0)") }) |> catch { SignalProducer<T, NoError>.empty } } Reac%veChess - Javier Soto, RACDC 2015 33
  20. 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
  21. skipRepeatedArrays() public func skipRepeatedArrays<T: Equatable, E>(signal: Signal<[T], E>) -> Signal<[T],

    E> { return signal |> skipRepeats(==) } Reac%veChess - Javier Soto, RACDC 2015 35