Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Reac%veChess - Javier Soto, RACDC 2015 4

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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