SlideShare a Scribd company logo
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

More Related Content

What's hot (20)

PPTX
Avoiding callback hell in Node js using promises
Ankit Agarwal
 
PDF
State management in a GraphQL era
kristijanmkd
 
PDF
Asynchronous and event-driven Grails applications
Alvaro Sanchez-Mariscal
 
PDF
Testing view controllers with Quick and Nimble
Marcio Klepacz
 
PDF
Quick: Better Tests via Incremental Setup
Brian Gesiak
 
PDF
Practical Protocol-Oriented-Programming
Natasha Murashev
 
PDF
Testing Ember Apps: Managing Dependency
Matthew Beale
 
PDF
Complex Architectures in Ember
Matthew Beale
 
PDF
Angular promises and http
Alexe Bogdan
 
PDF
High Performance web apps in Om, React and ClojureScript
Leonardo Borges
 
PDF
Angular server-side communication
Alexe Bogdan
 
PDF
Reactive Thinking in iOS Development - Pedro Piñera Buendía - Codemotion Amst...
Codemotion
 
PDF
Asynchronous Programming FTW! 2 (with AnyEvent)
xSawyer
 
PDF
JavaScript Promise
Joseph Chiang
 
PDF
ESCMAScript 6: Get Ready For The Future. Now
Krzysztof Szafranek
 
PDF
Workshop 10: ECMAScript 6
Visual Engineering
 
PDF
Functional Reactive Programming in Clojurescript
Leonardo Borges
 
PDF
The Strange World of Javascript and all its little Asynchronous Beasts
Federico Galassi
 
PDF
Reactive, component 그리고 angular2
Jeado Ko
 
PDF
The Open Web and what it means
Robert Nyman
 
Avoiding callback hell in Node js using promises
Ankit Agarwal
 
State management in a GraphQL era
kristijanmkd
 
Asynchronous and event-driven Grails applications
Alvaro Sanchez-Mariscal
 
Testing view controllers with Quick and Nimble
Marcio Klepacz
 
Quick: Better Tests via Incremental Setup
Brian Gesiak
 
Practical Protocol-Oriented-Programming
Natasha Murashev
 
Testing Ember Apps: Managing Dependency
Matthew Beale
 
Complex Architectures in Ember
Matthew Beale
 
Angular promises and http
Alexe Bogdan
 
High Performance web apps in Om, React and ClojureScript
Leonardo Borges
 
Angular server-side communication
Alexe Bogdan
 
Reactive Thinking in iOS Development - Pedro Piñera Buendía - Codemotion Amst...
Codemotion
 
Asynchronous Programming FTW! 2 (with AnyEvent)
xSawyer
 
JavaScript Promise
Joseph Chiang
 
ESCMAScript 6: Get Ready For The Future. Now
Krzysztof Szafranek
 
Workshop 10: ECMAScript 6
Visual Engineering
 
Functional Reactive Programming in Clojurescript
Leonardo Borges
 
The Strange World of Javascript and all its little Asynchronous Beasts
Federico Galassi
 
Reactive, component 그리고 angular2
Jeado Ko
 
The Open Web and what it means
Robert Nyman
 

Similar to Intro to Reactive Programming with Swift (20)

PPTX
Rxjs ngvikings
Christoffer Noring
 
PDF
Advanced redux
Boris Dinkevich
 
PDF
RxJS - 封裝程式的藝術
名辰 洪
 
PDF
The evolution of asynchronous JavaScript
Alessandro Cinelli (cirpo)
 
PDF
Think Async: Asynchronous Patterns in NodeJS
Adam L Barrett
 
PPTX
Reactive Java (33rd Degree)
Tomasz Kowalczewski
 
PDF
Nevyn — Promise, It's Async! Swift Language User Group Lightning Talk 2015-09-24
Joachim Bengtsson
 
PDF
Cycle.js - A functional reactive UI framework
Nikos Kalogridis
 
PDF
Cycle.js - Functional reactive UI framework (Nikos Kalogridis)
GreeceJS
 
PPTX
Reactive Java (GeeCON 2014)
Tomasz Kowalczewski
 
PDF
Chaining and function composition with lodash / underscore
Nicolas Carlo
 
PPTX
Rxjs swetugg
Christoffer Noring
 
PDF
Rxjs kyivjs 2015
Alexander Mostovenko
 
PDF
Reactive Programming Patterns with RxSwift
Florent Pillet
 
PDF
rx.js make async programming simpler
Alexander Mostovenko
 
PDF
WebCamp:Front-end Developers Day. Александр Мостовенко "Rx.js - делаем асинхр...
GeeksLab Odessa
 
PDF
Rxjs vienna
Christoffer Noring
 
PDF
Reduxing like a pro
Boris Dinkevich
 
PDF
Understanding Asynchronous JavaScript
jnewmanux
 
PPTX
Avoiding Callback Hell with Async.js
cacois
 
Rxjs ngvikings
Christoffer Noring
 
Advanced redux
Boris Dinkevich
 
RxJS - 封裝程式的藝術
名辰 洪
 
The evolution of asynchronous JavaScript
Alessandro Cinelli (cirpo)
 
Think Async: Asynchronous Patterns in NodeJS
Adam L Barrett
 
Reactive Java (33rd Degree)
Tomasz Kowalczewski
 
Nevyn — Promise, It's Async! Swift Language User Group Lightning Talk 2015-09-24
Joachim Bengtsson
 
Cycle.js - A functional reactive UI framework
Nikos Kalogridis
 
Cycle.js - Functional reactive UI framework (Nikos Kalogridis)
GreeceJS
 
Reactive Java (GeeCON 2014)
Tomasz Kowalczewski
 
Chaining and function composition with lodash / underscore
Nicolas Carlo
 
Rxjs swetugg
Christoffer Noring
 
Rxjs kyivjs 2015
Alexander Mostovenko
 
Reactive Programming Patterns with RxSwift
Florent Pillet
 
rx.js make async programming simpler
Alexander Mostovenko
 
WebCamp:Front-end Developers Day. Александр Мостовенко "Rx.js - делаем асинхр...
GeeksLab Odessa
 
Rxjs vienna
Christoffer Noring
 
Reduxing like a pro
Boris Dinkevich
 
Understanding Asynchronous JavaScript
jnewmanux
 
Avoiding Callback Hell with Async.js
cacois
 
Ad

Recently uploaded (20)

PPTX
Writing Better Code - Helping Developers make Decisions.pptx
Lorraine Steyn
 
PPTX
Equipment Management Software BIS Safety UK.pptx
BIS Safety Software
 
PDF
Linux Certificate of Completion - LabEx Certificate
VICTOR MAESTRE RAMIREZ
 
PPTX
Agentic Automation: Build & Deploy Your First UiPath Agent
klpathrudu
 
DOCX
Import Data Form Excel to Tally Services
Tally xperts
 
PDF
Alexander Marshalov - How to use AI Assistants with your Monitoring system Q2...
VictoriaMetrics
 
PPTX
Agentic Automation Journey Series Day 2 – Prompt Engineering for UiPath Agents
klpathrudu
 
PDF
The 5 Reasons for IT Maintenance - Arna Softech
Arna Softech
 
PDF
유니티에서 Burst Compiler+ThreadedJobs+SIMD 적용사례
Seongdae Kim
 
PDF
Efficient, Automated Claims Processing Software for Insurers
Insurance Tech Services
 
PPTX
ChiSquare Procedure in IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
PDF
Build It, Buy It, or Already Got It? Make Smarter Martech Decisions
bbedford2
 
PDF
Mobile CMMS Solutions Empowering the Frontline Workforce
CryotosCMMSSoftware
 
PPTX
Tally software_Introduction_Presentation
AditiBansal54083
 
PPTX
In From the Cold: Open Source as Part of Mainstream Software Asset Management
Shane Coughlan
 
PPTX
Why Businesses Are Switching to Open Source Alternatives to Crystal Reports.pptx
Varsha Nayak
 
PPTX
Fundamentals_of_Microservices_Architecture.pptx
MuhammadUzair504018
 
PPTX
Human Resources Information System (HRIS)
Amity University, Patna
 
PDF
HiHelloHR – Simplify HR Operations for Modern Workplaces
HiHelloHR
 
PDF
Automate Cybersecurity Tasks with Python
VICTOR MAESTRE RAMIREZ
 
Writing Better Code - Helping Developers make Decisions.pptx
Lorraine Steyn
 
Equipment Management Software BIS Safety UK.pptx
BIS Safety Software
 
Linux Certificate of Completion - LabEx Certificate
VICTOR MAESTRE RAMIREZ
 
Agentic Automation: Build & Deploy Your First UiPath Agent
klpathrudu
 
Import Data Form Excel to Tally Services
Tally xperts
 
Alexander Marshalov - How to use AI Assistants with your Monitoring system Q2...
VictoriaMetrics
 
Agentic Automation Journey Series Day 2 – Prompt Engineering for UiPath Agents
klpathrudu
 
The 5 Reasons for IT Maintenance - Arna Softech
Arna Softech
 
유니티에서 Burst Compiler+ThreadedJobs+SIMD 적용사례
Seongdae Kim
 
Efficient, Automated Claims Processing Software for Insurers
Insurance Tech Services
 
ChiSquare Procedure in IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
Build It, Buy It, or Already Got It? Make Smarter Martech Decisions
bbedford2
 
Mobile CMMS Solutions Empowering the Frontline Workforce
CryotosCMMSSoftware
 
Tally software_Introduction_Presentation
AditiBansal54083
 
In From the Cold: Open Source as Part of Mainstream Software Asset Management
Shane Coughlan
 
Why Businesses Are Switching to Open Source Alternatives to Crystal Reports.pptx
Varsha Nayak
 
Fundamentals_of_Microservices_Architecture.pptx
MuhammadUzair504018
 
Human Resources Information System (HRIS)
Amity University, Patna
 
HiHelloHR – Simplify HR Operations for Modern Workplaces
HiHelloHR
 
Automate Cybersecurity Tasks with Python
VICTOR MAESTRE RAMIREZ
 
Ad

Intro to Reactive Programming with Swift

  • 1. Introduction to Reactive Programming with RxSwift Xinran Wang Software Engineer Digital Home, Comcast NBCUniversal
  • 2. What is Reactive Programming? Programming with asynchronous data streams Treat events as sequences (streams) of data
  • 3. Improve API client retries 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 })
  • 7. Streams of data (think Swift sequences + async) Observable
  • 8. Event An element/value of the 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 data stream 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 the fun stuff: what do I do with these data streams?
  • 16. 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 …
  • 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 { observer in 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 and how would I actually use this for building my iOS app?
  • 33. 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 })
  • 34. 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 })
  • 35. 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 })
  • 36. 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 })
  • 37. 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 })
  • 38. 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 })
  • 39. 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 } } }
  • 40. 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 } } }
  • 41. 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 } } }
  • 42. 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 } } }
  • 43. 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 } } }
  • 44. 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 } } }
  • 45. 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 }
  • 46. 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 }
  • 47. 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 }
  • 49. Final Thoughts 1. 👎 Avoid nesting subscriptions
  • 50. 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)
  • 51. 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)
  • 52. Final Thoughts 2. Makes networking cleaner
  • 53. Final Thoughts 3. Rx forces your code to be more functional
  • 54. Final Thoughts 4. Can go 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