KEMBAR78
Intro to Reactive Programming with Swift | PDF
Introduction to Reactive
Programming with RxSwift
Xinran Wang
Software Engineer
Digital Home, Comcast NBCUniversal
What is Reactive Programming?
Programming with asynchronous data streams
Treat events as sequences (streams) of data
Improve API client retries and error handling
Simplify app auth management
someAsyncFunction {
anotherAsyncFunction1 {
anotherAsyncFunction2 {
anotherAsyncFunction3 {
anotherAsyncFunction4 {
// Thank God this has no error handling 😰
}
}
}
}
}
asyncObservable()
.flatMap { _ in return asyncObservable2() }
.flatMap { _ in return asyncObservable3() }
.flatMap { _ in return asyncObservable4() }
.subscribe(onNext: { data in
// handle last result
}, onError{ error in
// handle errors
})
Main Terminology
(the boring stuff)
Streams of data
(think Swift sequences + async)
Observable
Event
An element/value of the data stream
onNext:
onError:
onComplete:
Observer/Subscriber
Subscribes to the … Observable
Handles Events (the data stream elements)
Stream starts on“subscription”
Disposable
Disconnects the data stream
Ends Event ingestion
Handles cleanup of resources
Main Terminology
- Observable
- Event
- Subscriber
- Disposable
Icons made by https://www.flaticon.com/authors/smashicons from www.flaticon.com
Main Terminology
- Observable
- Event
- Subscriber
- Disposable
Main Terminology
- Observable
- Event
- Subscriber
- Disposable
Main Terminology
- Observable
- Event
- Subscriber
- Disposable
Ok, now the fun stuff:
what do I do with these data streams?
Rx Operators!
(the fun stuff)
// Creating:
create, from, just …
// Transforming
map, flatMap, buffer …
// Filtering
filter, first, skip, debounce …
// Combining
merge, zip, combineLatest …
// Error Handling
catch, retry
// Utility
subscribe, delay, do …
// Conditional/Boolean
contains, all, skipUntil …
// Mathematical and Aggregation
count, max, min, reduce …
Creating!
// Creating:
create, from, just …
// Transforming
map, flatMap, buffer …
// Filtering
filter, first, skip, debounce …
// Combining
merge, zip, combineLatest …
// Error Handling
catch, retry
// Utility
subscribe, delay, do …
// Conditional/Boolean
contains, all, skipUntil …
// Mathematical and Aggregation
count, max, min, reduce …
Observable.create { observer in
asyncFunction(completion: {
// create the event for each piece of data
// observer.onNext()
// observer.onError()
// observer.onComplete()
})
return Disposables.create()
}
func makeSushi(completion: @escaping (Sushi?, Error?)->Void)
func makeSushiObservable() -> Observable<Sushi?> {
return Observable.create { observer in
self.makeSushi { (sushi, error) in
if let error = error {
observer.onError(error)
}
observer.onNext(sushi)
observer.onCompleted()
}
return Disposables.create()
}
}
func makeSushi(completion: @escaping (Sushi?, Error?)->Void)
func makeSushiObservable() -> Observable<Sushi?> {
return Observable.create { observer in
self.makeSushi { (sushi, error) in
if let error = error {
observer.onError(error)
}
observer.onNext(sushi)
observer.onCompleted()
}
return Disposables.create()
}
}
func makeSushi(completion: @escaping (Sushi?, Error?)->Void)
func makeSushiObservable() -> Observable<Sushi?> {
return Observable.create { observer in
self.makeSushi { (sushi, error) in
if let error = error {
observer.onError(error)
}
observer.onNext(sushi)
observer.onCompleted()
}
return Disposables.create()
}
}
func makeSushi(completion: @escaping (Sushi?, Error?)->Void)
func makeSushiObservable() -> Observable<Sushi?> {
return Observable.create { observer in
self.makeSushi { (sushi, error) in
if let error = error {
observer.onError(error)
}
observer.onNext(sushi)
observer.onCompleted()
}
return Disposables.create()
}
}
func makeSushi(completion: @escaping (Sushi?, Error?)->Void)
func makeSushiObservable() -> Observable<Sushi?> {
return Observable.create { observer in
self.makeSushi { (sushi, error) in
if let error = error {
observer.onError(error)
}
observer.onNext(sushi)
observer.onCompleted()
}
return Disposables.create()
}
}
func makeSushi(completion: @escaping (Sushi?, Error?)->Void)
func makeSushiObservable() -> Observable<Sushi?> {
return Observable.create { observer in
self.makeSushi { (sushi, error) in
if let error = error {
observer.onError(error)
}
observer.onNext(sushi)
observer.onCompleted()
}
return Disposables.create()
}
}
func makeSushi(completion: @escaping (Sushi?, Error?)->Void)
func makeSushiObservable() -> Observable<Sushi?> {
return Observable.create { observer in
self.makeSushi { (sushi, error) in
if let error = error {
observer.onError(error)
}
observer.onNext(sushi)
observer.onCompleted()
}
return Disposables.create()
}
}
func makeSushiObservable() -> Observable<Sushi?> {
return Observable.create { observer in
self.makeSushi { (sushi, error) in
if let error = error {
observer.onError(error)
}
observer.onNext(sushi)
observer.onCompleted()
}
return Disposables.create()
}
}
makeSushiObservable()
// observable stream STARTS on subscription
// so makeSushi actually first gets called here
.subscribe(onNext: { sushi in
// eat the sushi!
}, onError: { error in
// complain to the waiter
})
.disposed(by: disposeBag)
func makeSushiObservable() -> Observable<Sushi?> {
return Observable.create { observer in
self.makeSushi { (sushi, error) in
if let error = error {
observer.onError(error)
}
observer.onNext(sushi)
observer.onCompleted()
}
return Disposables.create()
}
}
makeSushiObservable()
// observable stream STARTS on subscription
// so makeSushi actually first gets called here
.subscribe(onNext: { sushi in
// eat the sushi!
}, onError: { error in
// complain to the waiter
})
.disposed(by: disposeBag)
More Operators!
// Creating:
create, from, just …
// Transforming
map, flatMap, buffer …
// Filtering
filter, first, skip, debounce …
// Combining
merge, zip, combineLatest …
// Error Handling
catch, retry
// Utility
subscribe, delay, do …
// Conditional/Boolean
contains, all, skipUntil …
// Mathematical and Aggregation
count, max, min, reduce …
let sushi = Observable.zip(makeRice(), sliceFish(tuna)) { (rice, tuna) -> Sushi in
return Sushi(rice, tuna)
}
sushi.filter { sushi in
return !sushi.isVegetarian()
}
.map { (sushi: Sushi) -> Sushi in
return addSoySauce(sushi)
}
.map { addWasabi }
let sushi = Observable.zip(makeRice(), sliceFish(tuna)) { (rice, tuna) -> Sushi in
return Sushi(rice, tuna)
}
sushi.filter { sushi in
return !sushi.isVegetarian()
}
.map { (sushi: Sushi) -> Sushi in
return addSoySauce(sushi)
}
.map { addWasabi }
Cool …
Why and how would I actually use
this for building my iOS app?
Networking
Unified Error Handling & Better Retries
class APIClient {
static func responseObservable(request: URLRequest)
-> Observable<[String: Any]> {
return rxResponse(request)
.flatMap { handleResponse }
// .retry(3)
.retryWhen { handleErrors }
}
}
APIClient.responseObservable(request: req)
.subscribe(onNext: { data in
// handle data
})
class APIClient {
static func responseObservable(request: URLRequest)
-> Observable<[String: Any]> {
return rxResponse(request)
.flatMap { handleResponse }
// .retry(3)
.retryWhen { handleErrors }
}
}
APIClient.responseObservable(request: req)
.subscribe(onNext: { data in
// handle data
})
class APIClient {
static func responseObservable(request: URLRequest)
-> Observable<[String: Any]> {
return rxResponse(request)
.flatMap { handleResponse }
// .retry(3)
.retryWhen { handleErrors }
}
}
APIClient.responseObservable(request: req)
.subscribe(onNext: { data in
// handle data
})
class APIClient {
static func responseObservable(request: URLRequest)
-> Observable<[String: Any]> {
return rxResponse(request)
.flatMap { handleResponse }
// .retry(3)
.retryWhen { handleErrors }
}
}
APIClient.responseObservable(request: req)
.subscribe(onNext: { data in
// handle data
})
class APIClient {
static func responseObservable(request: URLRequest)
-> Observable<[String: Any]> {
return rxResponse(request)
.flatMap { handleResponse }
.retry(3)
.retryWhen { handleErrors }
}
}
APIClient.responseObservable(request: req)
.subscribe(onNext: { data in
// handle data
})
class APIClient {
static func responseObservable(request: URLRequest)
-> Observable<[String: Any]> {
return rxResponse(request)
.flatMap { handleResponse }
// .retry(3)
.retryWhen { handleErrors }
}
}
APIClient.responseObservable(request: req)
.subscribe(onNext: { data in
// handle data
})
private static func handleResponse(
data: Data?, response: URLResponse?
) throws -> Observable<[String: Any]> {
guard let httpResponse = response as? HTTPURLResponse else {
throw APIError.invalidResponse(response)
}
guard 200..<300 ~= httpResponse.statusCode else {
throw APIError.badStatusCode(httpResponse)
}
guard let responseData = data else {
throw APIError.badData(data, httpResponse)
}
if let json = try JSONSerialization
.jsonObject(with: responseData, options: []) as? [String: Any] {
return Observable.just(json)
} else {
throw APIError.jsonParsingError
}
}
class APIClient {
let apiClientDisposeBag = DisposeBag()
static func responseObservable(
request: URLRequest
) -> Observable<[String: Any]> {
return customRequest(request)
.flatMap { handleResponse }
// .retry(3)
.retryWhen { handleErrors }
}
}
private static func handleResponse(
data: Data?, response: URLResponse?
) throws -> Observable<[String: Any]> {
guard let httpResponse = response as? HTTPURLResponse else {
throw APIError.invalidResponse(response)
}
guard 200..<300 ~= httpResponse.statusCode else {
throw APIError.badStatusCode(httpResponse)
}
guard let responseData = data else {
throw APIError.badData(data, httpResponse)
}
if let json = try JSONSerialization
.jsonObject(with: responseData, options: []) as? [String: Any] {
return Observable.just(json)
} else {
throw APIError.jsonParsingError
}
}
class APIClient {
let apiClientDisposeBag = DisposeBag()
static func responseObservable(
request: URLRequest
) -> Observable<[String: Any]> {
return customRequest(request)
.flatMap { handleResponse }
// .retry(3)
.retryWhen { handleErrors }
}
}
private static func handleErrors(errors: Observable<Error>)
-> Observable<Void> {
return errors.enumerated()
.flatMap { (i, error) -> Observable<Void> in
// handle max number of retries based on `i`
guard i < 3 else {
return Observable.error(error) // propagate error
}
switch error {
case .expiredToken:
self.refreshToken()
return Observable.just(()) // <- forces retry
default:
return Observable.just(()) // <- forces retry
}
}
}
class APIClient {
let apiClientDisposeBag = DisposeBag()
static func responseObservable(
request: URLRequest
) -> Observable<[String: Any]> {
return customRequest(request)
.flatMap { handleResponse }
// .retry(3)
.retryWhen { handleErrors }
}
}
private static func handleErrors(errors: Observable<Error>)
-> Observable<Void> {
return errors.enumerated()
.flatMap { (i, error) -> Observable<Void> in
// handle max number of retries based on `i`
guard i < 3 else {
return Observable.error(error) // propagate error
}
switch error {
case .expiredToken:
self.refreshToken()
return Observable.just(()) // <- forces retry
default:
return Observable.just(()) // <- forces retry
}
}
}
class APIClient {
let apiClientDisposeBag = DisposeBag()
static func responseObservable(
request: URLRequest
) -> Observable<[String: Any]> {
return customRequest(request)
.flatMap { handleResponse }
// .retry(3)
.retryWhen { handleErrors }
}
}
private static func handleErrors(errors: Observable<Error>)
-> Observable<Void> {
return errors.enumerated()
.flatMap { (i, error) -> Observable<Void> in
// handle max number of retries based on `i`
guard i < 3 else {
return Observable.error(error) // propagate error
}
switch error {
case .expiredToken:
self.refreshToken()
return Observable.just(()) // <- forces retry
default:
return Observable.just(()) // <- forces retry
}
}
}
class APIClient {
let apiClientDisposeBag = DisposeBag()
static func responseObservable(
request: URLRequest
) -> Observable<[String: Any]> {
return customRequest(request)
.flatMap { handleResponse }
// .retry(3)
.retryWhen { handleErrors }
}
}
private static func handleErrors(errors: Observable<Error>)
-> Observable<Void> {
return errors.enumerated()
.flatMap { (i, error) -> Observable<Void> in
// handle max number of retries based on `i`
guard i < 3 else {
return Observable.error(error) // propagate error
}
switch error {
case .expiredToken:
self.refreshToken()
return Observable.just(()) // <- forces retry
default:
return Observable.just(()) // <- forces retry
}
}
}
class APIClient {
let apiClientDisposeBag = DisposeBag()
static func responseObservable(
request: URLRequest
) -> Observable<[String: Any]> {
return customRequest(request)
.flatMap { handleResponse }
// .retry(3)
.retryWhen { handleErrors }
}
}
class APIClient {
static func responseObservable(request: URLRequest)
-> Observable<[String: Any]> {
...
}
static func response(request: URLRequest, completion: @escaping ([String: Any])->Void) {
responseObservable(request: request)
.subscribe(onNext: { data in
completion(data)
})
.disposed(by: apiClientDisposeBag)
}
}
APIClient.response(request: req) { json in
// do stuff with json
}
class APIClient {
static func responseObservable(request: URLRequest)
-> Observable<[String: Any]> {
...
}
static func response(request: URLRequest, completion: @escaping ([String: Any])->Void) {
responseObservable(request: request)
.subscribe(onNext: { data in
completion(data)
})
.disposed(by: apiClientDisposeBag)
}
}
APIClient.response(request: req) { json in
// do stuff with json
}
class APIClient {
static func responseObservable(request: URLRequest)
-> Observable<[String: Any]> {
...
}
static func response(request: URLRequest, completion: @escaping ([String: Any])->Void) {
responseObservable(request: request)
.subscribe(onNext: { data in
completion(data)
})
.disposed(by: apiClientDisposeBag)
}
}
APIClient.response(request: req) { json in
// do stuff with json
}
Final Thoughts
Final Thoughts
1. 👎 Avoid nesting subscriptions
APIClient.fetchMovie(id).subscribe(onNext: { movie in
APIClient.fetchMovieDetails(movie).subscribe(onNext: { details in
...
})
.addDisposableTo(disposeBag)
})
.addDisposableTo(disposeBag)
APIClient.fetchMovie(id)
.flatMap { id in
return APIClient.fetchMovieDetails(movie)
}
.subscribe(onNext: { details in
...
})
.addDisposableTo(disposeBag)
APIClient.fetchMovie(id).subscribe(onNext: { movie in
APIClient.fetchMovieDetails(movie).subscribe(onNext: { details in
...
})
.addDisposableTo(disposeBag)
})
.addDisposableTo(disposeBag)
APIClient.fetchMovie(id)
.flatMap { id in
return APIClient.fetchMovieDetails(movie)
}
.subscribe(onNext: { details in
...
})
.addDisposableTo(disposeBag)
Final Thoughts
2. Makes networking cleaner
Final Thoughts
3. Rx forces your code to be more functional
Final Thoughts
4. Can go from callbacks to observable
The whole project does not need to be observable
Useful Resources
General:
- http://reactivex.io/documentation/observable.html
- http://rxmarbles.com/
RxSwift-specific:
- https://github.com/ReactiveX/RxSwift
- https://egghead.io/courses/introduction-to-reactive-programming
- https://www.raywenderlich.com/138547/getting-started-with-rxswift-and-rxcocoa
- https://www.raywenderlich.com/158026/introducing-rxswift-reactive-programming-swift
Thanks!
www.xinran-wang.com
www.linkedin.com/in/xinranw
www.github.com/xinranw
@xw92

Intro to Reactive Programming with Swift

  • 1.
    Introduction to Reactive Programmingwith RxSwift Xinran Wang Software Engineer Digital Home, Comcast NBCUniversal
  • 2.
    What is ReactiveProgramming? Programming with asynchronous data streams Treat events as sequences (streams) of data
  • 3.
    Improve API clientretries and error handling Simplify app auth management
  • 4.
    someAsyncFunction { anotherAsyncFunction1 { anotherAsyncFunction2{ anotherAsyncFunction3 { anotherAsyncFunction4 { // Thank God this has no error handling 😰 } } } } }
  • 5.
    asyncObservable() .flatMap { _in return asyncObservable2() } .flatMap { _ in return asyncObservable3() } .flatMap { _ in return asyncObservable4() } .subscribe(onNext: { data in // handle last result }, onError{ error in // handle errors })
  • 6.
  • 7.
    Streams of data (thinkSwift sequences + async) Observable
  • 8.
    Event An element/value ofthe data stream onNext: onError: onComplete:
  • 9.
    Observer/Subscriber Subscribes to the… Observable Handles Events (the data stream elements) Stream starts on“subscription”
  • 10.
    Disposable Disconnects the datastream Ends Event ingestion Handles cleanup of resources
  • 11.
    Main Terminology - Observable -Event - Subscriber - Disposable Icons made by https://www.flaticon.com/authors/smashicons from www.flaticon.com
  • 12.
    Main Terminology - Observable -Event - Subscriber - Disposable
  • 13.
    Main Terminology - Observable -Event - Subscriber - Disposable
  • 14.
    Main Terminology - Observable -Event - Subscriber - Disposable
  • 15.
    Ok, now thefun stuff: what do I do with these data streams?
  • 16.
    Rx Operators! (the funstuff) // Creating: create, from, just … // Transforming map, flatMap, buffer … // Filtering filter, first, skip, debounce … // Combining merge, zip, combineLatest … // Error Handling catch, retry // Utility subscribe, delay, do … // Conditional/Boolean contains, all, skipUntil … // Mathematical and Aggregation count, max, min, reduce …
  • 17.
    Creating! // Creating: create, from,just … // Transforming map, flatMap, buffer … // Filtering filter, first, skip, debounce … // Combining merge, zip, combineLatest … // Error Handling catch, retry // Utility subscribe, delay, do … // Conditional/Boolean contains, all, skipUntil … // Mathematical and Aggregation count, max, min, reduce …
  • 18.
    Observable.create { observerin asyncFunction(completion: { // create the event for each piece of data // observer.onNext() // observer.onError() // observer.onComplete() }) return Disposables.create() }
  • 19.
    func makeSushi(completion: @escaping(Sushi?, Error?)->Void) func makeSushiObservable() -> Observable<Sushi?> { return Observable.create { observer in self.makeSushi { (sushi, error) in if let error = error { observer.onError(error) } observer.onNext(sushi) observer.onCompleted() } return Disposables.create() } }
  • 20.
    func makeSushi(completion: @escaping(Sushi?, Error?)->Void) func makeSushiObservable() -> Observable<Sushi?> { return Observable.create { observer in self.makeSushi { (sushi, error) in if let error = error { observer.onError(error) } observer.onNext(sushi) observer.onCompleted() } return Disposables.create() } }
  • 21.
    func makeSushi(completion: @escaping(Sushi?, Error?)->Void) func makeSushiObservable() -> Observable<Sushi?> { return Observable.create { observer in self.makeSushi { (sushi, error) in if let error = error { observer.onError(error) } observer.onNext(sushi) observer.onCompleted() } return Disposables.create() } }
  • 22.
    func makeSushi(completion: @escaping(Sushi?, Error?)->Void) func makeSushiObservable() -> Observable<Sushi?> { return Observable.create { observer in self.makeSushi { (sushi, error) in if let error = error { observer.onError(error) } observer.onNext(sushi) observer.onCompleted() } return Disposables.create() } }
  • 23.
    func makeSushi(completion: @escaping(Sushi?, Error?)->Void) func makeSushiObservable() -> Observable<Sushi?> { return Observable.create { observer in self.makeSushi { (sushi, error) in if let error = error { observer.onError(error) } observer.onNext(sushi) observer.onCompleted() } return Disposables.create() } }
  • 24.
    func makeSushi(completion: @escaping(Sushi?, Error?)->Void) func makeSushiObservable() -> Observable<Sushi?> { return Observable.create { observer in self.makeSushi { (sushi, error) in if let error = error { observer.onError(error) } observer.onNext(sushi) observer.onCompleted() } return Disposables.create() } }
  • 25.
    func makeSushi(completion: @escaping(Sushi?, Error?)->Void) func makeSushiObservable() -> Observable<Sushi?> { return Observable.create { observer in self.makeSushi { (sushi, error) in if let error = error { observer.onError(error) } observer.onNext(sushi) observer.onCompleted() } return Disposables.create() } }
  • 26.
    func makeSushiObservable() ->Observable<Sushi?> { return Observable.create { observer in self.makeSushi { (sushi, error) in if let error = error { observer.onError(error) } observer.onNext(sushi) observer.onCompleted() } return Disposables.create() } } makeSushiObservable() // observable stream STARTS on subscription // so makeSushi actually first gets called here .subscribe(onNext: { sushi in // eat the sushi! }, onError: { error in // complain to the waiter }) .disposed(by: disposeBag)
  • 27.
    func makeSushiObservable() ->Observable<Sushi?> { return Observable.create { observer in self.makeSushi { (sushi, error) in if let error = error { observer.onError(error) } observer.onNext(sushi) observer.onCompleted() } return Disposables.create() } } makeSushiObservable() // observable stream STARTS on subscription // so makeSushi actually first gets called here .subscribe(onNext: { sushi in // eat the sushi! }, onError: { error in // complain to the waiter }) .disposed(by: disposeBag)
  • 28.
    More Operators! // Creating: create,from, just … // Transforming map, flatMap, buffer … // Filtering filter, first, skip, debounce … // Combining merge, zip, combineLatest … // Error Handling catch, retry // Utility subscribe, delay, do … // Conditional/Boolean contains, all, skipUntil … // Mathematical and Aggregation count, max, min, reduce …
  • 29.
    let sushi =Observable.zip(makeRice(), sliceFish(tuna)) { (rice, tuna) -> Sushi in return Sushi(rice, tuna) } sushi.filter { sushi in return !sushi.isVegetarian() } .map { (sushi: Sushi) -> Sushi in return addSoySauce(sushi) } .map { addWasabi }
  • 30.
    let sushi =Observable.zip(makeRice(), sliceFish(tuna)) { (rice, tuna) -> Sushi in return Sushi(rice, tuna) } sushi.filter { sushi in return !sushi.isVegetarian() } .map { (sushi: Sushi) -> Sushi in return addSoySauce(sushi) } .map { addWasabi }
  • 31.
    Cool … Why andhow would I actually use this for building my iOS app?
  • 32.
  • 33.
    class APIClient { staticfunc responseObservable(request: URLRequest) -> Observable<[String: Any]> { return rxResponse(request) .flatMap { handleResponse } // .retry(3) .retryWhen { handleErrors } } } APIClient.responseObservable(request: req) .subscribe(onNext: { data in // handle data })
  • 34.
    class APIClient { staticfunc responseObservable(request: URLRequest) -> Observable<[String: Any]> { return rxResponse(request) .flatMap { handleResponse } // .retry(3) .retryWhen { handleErrors } } } APIClient.responseObservable(request: req) .subscribe(onNext: { data in // handle data })
  • 35.
    class APIClient { staticfunc responseObservable(request: URLRequest) -> Observable<[String: Any]> { return rxResponse(request) .flatMap { handleResponse } // .retry(3) .retryWhen { handleErrors } } } APIClient.responseObservable(request: req) .subscribe(onNext: { data in // handle data })
  • 36.
    class APIClient { staticfunc responseObservable(request: URLRequest) -> Observable<[String: Any]> { return rxResponse(request) .flatMap { handleResponse } // .retry(3) .retryWhen { handleErrors } } } APIClient.responseObservable(request: req) .subscribe(onNext: { data in // handle data })
  • 37.
    class APIClient { staticfunc responseObservable(request: URLRequest) -> Observable<[String: Any]> { return rxResponse(request) .flatMap { handleResponse } .retry(3) .retryWhen { handleErrors } } } APIClient.responseObservable(request: req) .subscribe(onNext: { data in // handle data })
  • 38.
    class APIClient { staticfunc responseObservable(request: URLRequest) -> Observable<[String: Any]> { return rxResponse(request) .flatMap { handleResponse } // .retry(3) .retryWhen { handleErrors } } } APIClient.responseObservable(request: req) .subscribe(onNext: { data in // handle data })
  • 39.
    private static funchandleResponse( data: Data?, response: URLResponse? ) throws -> Observable<[String: Any]> { guard let httpResponse = response as? HTTPURLResponse else { throw APIError.invalidResponse(response) } guard 200..<300 ~= httpResponse.statusCode else { throw APIError.badStatusCode(httpResponse) } guard let responseData = data else { throw APIError.badData(data, httpResponse) } if let json = try JSONSerialization .jsonObject(with: responseData, options: []) as? [String: Any] { return Observable.just(json) } else { throw APIError.jsonParsingError } } class APIClient { let apiClientDisposeBag = DisposeBag() static func responseObservable( request: URLRequest ) -> Observable<[String: Any]> { return customRequest(request) .flatMap { handleResponse } // .retry(3) .retryWhen { handleErrors } } }
  • 40.
    private static funchandleResponse( data: Data?, response: URLResponse? ) throws -> Observable<[String: Any]> { guard let httpResponse = response as? HTTPURLResponse else { throw APIError.invalidResponse(response) } guard 200..<300 ~= httpResponse.statusCode else { throw APIError.badStatusCode(httpResponse) } guard let responseData = data else { throw APIError.badData(data, httpResponse) } if let json = try JSONSerialization .jsonObject(with: responseData, options: []) as? [String: Any] { return Observable.just(json) } else { throw APIError.jsonParsingError } } class APIClient { let apiClientDisposeBag = DisposeBag() static func responseObservable( request: URLRequest ) -> Observable<[String: Any]> { return customRequest(request) .flatMap { handleResponse } // .retry(3) .retryWhen { handleErrors } } }
  • 41.
    private static funchandleErrors(errors: Observable<Error>) -> Observable<Void> { return errors.enumerated() .flatMap { (i, error) -> Observable<Void> in // handle max number of retries based on `i` guard i < 3 else { return Observable.error(error) // propagate error } switch error { case .expiredToken: self.refreshToken() return Observable.just(()) // <- forces retry default: return Observable.just(()) // <- forces retry } } } class APIClient { let apiClientDisposeBag = DisposeBag() static func responseObservable( request: URLRequest ) -> Observable<[String: Any]> { return customRequest(request) .flatMap { handleResponse } // .retry(3) .retryWhen { handleErrors } } }
  • 42.
    private static funchandleErrors(errors: Observable<Error>) -> Observable<Void> { return errors.enumerated() .flatMap { (i, error) -> Observable<Void> in // handle max number of retries based on `i` guard i < 3 else { return Observable.error(error) // propagate error } switch error { case .expiredToken: self.refreshToken() return Observable.just(()) // <- forces retry default: return Observable.just(()) // <- forces retry } } } class APIClient { let apiClientDisposeBag = DisposeBag() static func responseObservable( request: URLRequest ) -> Observable<[String: Any]> { return customRequest(request) .flatMap { handleResponse } // .retry(3) .retryWhen { handleErrors } } }
  • 43.
    private static funchandleErrors(errors: Observable<Error>) -> Observable<Void> { return errors.enumerated() .flatMap { (i, error) -> Observable<Void> in // handle max number of retries based on `i` guard i < 3 else { return Observable.error(error) // propagate error } switch error { case .expiredToken: self.refreshToken() return Observable.just(()) // <- forces retry default: return Observable.just(()) // <- forces retry } } } class APIClient { let apiClientDisposeBag = DisposeBag() static func responseObservable( request: URLRequest ) -> Observable<[String: Any]> { return customRequest(request) .flatMap { handleResponse } // .retry(3) .retryWhen { handleErrors } } }
  • 44.
    private static funchandleErrors(errors: Observable<Error>) -> Observable<Void> { return errors.enumerated() .flatMap { (i, error) -> Observable<Void> in // handle max number of retries based on `i` guard i < 3 else { return Observable.error(error) // propagate error } switch error { case .expiredToken: self.refreshToken() return Observable.just(()) // <- forces retry default: return Observable.just(()) // <- forces retry } } } class APIClient { let apiClientDisposeBag = DisposeBag() static func responseObservable( request: URLRequest ) -> Observable<[String: Any]> { return customRequest(request) .flatMap { handleResponse } // .retry(3) .retryWhen { handleErrors } } }
  • 45.
    class APIClient { staticfunc responseObservable(request: URLRequest) -> Observable<[String: Any]> { ... } static func response(request: URLRequest, completion: @escaping ([String: Any])->Void) { responseObservable(request: request) .subscribe(onNext: { data in completion(data) }) .disposed(by: apiClientDisposeBag) } } APIClient.response(request: req) { json in // do stuff with json }
  • 46.
    class APIClient { staticfunc responseObservable(request: URLRequest) -> Observable<[String: Any]> { ... } static func response(request: URLRequest, completion: @escaping ([String: Any])->Void) { responseObservable(request: request) .subscribe(onNext: { data in completion(data) }) .disposed(by: apiClientDisposeBag) } } APIClient.response(request: req) { json in // do stuff with json }
  • 47.
    class APIClient { staticfunc responseObservable(request: URLRequest) -> Observable<[String: Any]> { ... } static func response(request: URLRequest, completion: @escaping ([String: Any])->Void) { responseObservable(request: request) .subscribe(onNext: { data in completion(data) }) .disposed(by: apiClientDisposeBag) } } APIClient.response(request: req) { json in // do stuff with json }
  • 48.
  • 49.
    Final Thoughts 1. 👎Avoid nesting subscriptions
  • 50.
    APIClient.fetchMovie(id).subscribe(onNext: { moviein APIClient.fetchMovieDetails(movie).subscribe(onNext: { details in ... }) .addDisposableTo(disposeBag) }) .addDisposableTo(disposeBag) APIClient.fetchMovie(id) .flatMap { id in return APIClient.fetchMovieDetails(movie) } .subscribe(onNext: { details in ... }) .addDisposableTo(disposeBag)
  • 51.
    APIClient.fetchMovie(id).subscribe(onNext: { moviein APIClient.fetchMovieDetails(movie).subscribe(onNext: { details in ... }) .addDisposableTo(disposeBag) }) .addDisposableTo(disposeBag) APIClient.fetchMovie(id) .flatMap { id in return APIClient.fetchMovieDetails(movie) } .subscribe(onNext: { details in ... }) .addDisposableTo(disposeBag)
  • 52.
    Final Thoughts 2. Makesnetworking cleaner
  • 53.
    Final Thoughts 3. Rxforces your code to be more functional
  • 54.
    Final Thoughts 4. Cango from callbacks to observable The whole project does not need to be observable
  • 55.
    Useful Resources General: - http://reactivex.io/documentation/observable.html -http://rxmarbles.com/ RxSwift-specific: - https://github.com/ReactiveX/RxSwift - https://egghead.io/courses/introduction-to-reactive-programming - https://www.raywenderlich.com/138547/getting-started-with-rxswift-and-rxcocoa - https://www.raywenderlich.com/158026/introducing-rxswift-reactive-programming-swift
  • 56.