SlideShare une entreprise Scribd logo
1  sur  38
Télécharger pour lire hors ligne
PRESCRIBING RX RESPONSIBLY 💊
2 0 1 7
2
AGENDA
01RX INTRO
02WHEN TO USE RX (OR NOT)
03RX BEST PRACTICES
04CONCLUSION AND
TAKEAWAYS
WHAT IS RX?
4
today’s talk
not today’s
talk
RX-BERG
W H A T I S R X ?
5
•RxSwift - Swift implementation of ReactiveX
•Follows the “Observer pattern”
•Declarative way of defining the data flow in your app
•Avoid “callback hell”
•Data flow is handled via manageable streams
W H A T I S R X ?
6
STREAMS
Observable<WaterMolecule>
Observable<Bool>
Observable<MeetUp>
of things.
One thing at a time.
W H A T I S R X ?
7RxMarbles.com
W H A T I S R X ?
8RxMarbles.com
W H A T I S R X ?
9
Observable
RX ECOSYSTEM
Variable
Subject
PublishSubject
Driver
DisposeBag
BehaviorSubject
Observer
W H A T I S R X ?
10
Observable
RX ECOSYSTEM
Variable
Subject
PublishSubject
Driver
DisposeBag
BehaviorSubject
Observer
WHEN TO USE RX
12
01User actions (button taps, text field delegates)
02Async operations (Network calls, processing)
03Bindings (VC!!<-> VM !!<-> Model)
L I S T
WHEN TO
USE RX
04Prevent code 🍝
B U T T O N A C T I O N
13
WITHOUT RX
@IBAction func logoTapped(_ sender: UIButton) {
dismissUntilHome()
}
navBar.logoButton !=> dismissUntilHome !!>>> rx_disposeBag
WITH RX
Drag and drop to create IBAction function. A bit more complicated if it is nested in a custom
view.
We are using Fira Code font: https://github.com/tonsky/FiraCode
D A T E P I C K E R
14
WITH RX
WITHOUT RX
Drag and drop to create IBAction function. A bit more complicated if it is nested in a custom
view, or number of date pickers are not constant.
datePicker.rx.date !=> viewModel.endDate !!>>> rx_disposeBag
@IBAction func datePicked(_ sender: UIDatePicker) {
viewModel.endDate = sender.date
}
T E X T F I E L D
15
WITH RX
titleField.textView.rx.text.orEmpty !!<-> viewModel.title !!>>> rx_disposeBag
Create binding in view controller.
WITHOUT RX
Set up delegate for the text field to listen for edit events to update view model, and manually
trigger UI update when view model’s property has changed.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String)
var title: String = "" {
didSet {
updateTextFields()
}
}
SCROLLING
UNDER
S C R O L L V I E W
S C R O L L V I E W
17
WITH RX
tableView.rx_scrolledUnderTop !=> viewModel.showTopGradient !!>>> rx_disposeBag
tableView.rx_scrolledUnderBottom !=> viewModel.showBottomGradient !!>>> rx_disposeBag
Create binding in view controller.
WITHOUT RX
Set up delegate extensions and do the calculation within the method, at multiple places for
multiple classes:
func scrollViewDidScroll(_ scrollView: UIScrollView)
P A G I N A T I O N
18
SET UP DATA CONTROLLER
func getPaginatedData<T: RealmSwift.Object>(resource: Resource, loadNextPageTrigger: Observable<Void>, dataParser: @escaping (Data) !-> ([T], Int)) !->
Observable<[T]> {
let existingObjects: [T] = Realm.ip_objects(type: T.self)!?.toArray() !?? []
return recursiveGetPaginatedData(resource: resource, lastModified: lastModifiedDate, dataParser: dataParser, loadedSoFar: [], page: 1, loadNextPageTrigger:
loadNextPageTrigger).startWith(existingObjects)
}
func recursiveGetPaginatedData<T: RealmSwift.Object>(resource: Resource, dataParser: @escaping (Data) !-> ([T], Int), loadedSoFar: [T], page: Int,
loadNextPageTrigger: Observable<Void>) !-> Observable<[T]> {
guard let urlRequest = URLRequest(builder: URLRequestBuilder(resource: resource, paginationPage: page, authenticationToken = authenticationToken) else {
return Observable.just(loadedSoFar)
}
return networkOperationQueue.add(dataRequest: urlRequest).observeOn(MainScheduler.instance)
.flatMap { data !-> Observable<[T]> in
var justLoaded = loadedSoFar
let (models, paginationTotalItems) = dataParser(data)
justLoaded.append(contentsOf: models)
if justLoaded.count !== paginationTotalItems {
Realm.ip_add(justLoaded, update: true, configuration: self.realmConfiguration)
return Observable.just(justLoaded)
}
return Observable.concat([
Observable.just(justLoaded),
Observable.never().takeUntil(loadNextPageTrigger),
Observable.deferred { self.recursiveGetPaginatedData(resource: resource, dataParser: dataParser, loadedSoFar: justLoaded, page: page + 1,
loadNextPageTrigger: loadNextPageTrigger) }
])
}
}
Functions of the network call in data controller:
P A G I N A T I O N
19
SET UP VIEW MODEL
func opportunities(loadNextPageTrigger: Observable<Void>) !-> Observable<[OpportunityModel]> {
return getPaginatedData(resource: Resource.opportunities, loadNextPageTrigger: loadNextPageTrigger) { (data) !-> ([OpportunityRealmModel], Int) in
let opportunitiesModel = try! OpportunitiesModel(node: data)
return (opportunitiesModel.opportunities, opportunitiesModel.total)
}
.map { $0 as [OpportunityModel] }
}
Function of the API call in data controller:
Where we make the API call in view model:
dataController.opportunities(loadNextPageTrigger: nextPageTrigger.asObservable())
.map { $0.map { OpportunityCellViewModel(opportunity: $0) } }
.subscribe(
onNext: {
self.opportunityCellViewModels = $0
self.hasMoreOpportunities = true
},
onError: {
Logger.error($0)
NotificationCenter.postMessage(type: .requestFailure)
self.hasMoreOpportunities = false
},
onCompleted: {
self.opportunityCellViewModels.append(EndOfListViewModel())
self.hasMoreOpportunities = false
}) !!>>> rx_disposeBag
P A G I N A T I O N
20
GET NEXT PAGE IN VIEW MODEL
func nextPage() {
nextPageTrigger.fire()
}
How we get the next page in the view model:
N E T W O R K C A L L S
21
CHAINED NETWORK CALLS
guard let s3Object = requestS3Object(for: .opportunity) else { return nil }
return s3Object.observeOn(MainScheduler.instance).flatMap { s3Object !-> Observable<Bool> in
opportunity.imageURL = URL(string: s3Object.publicURL)
opportunity.imageKey = s3Object.key
guard let presignedURL = URL(string: s3Object.presignedURL) else {
return Observable.error(RxURLSessionError.requestCreationError)
}
return self.uploadImage(data: imageData, to: presignedURL)
}.observeOn(MainScheduler.instance).flatMap { imageUploadSuccess !-> Observable<Data> in
requestBuilder.data = opportunity.toJson()
guard let urlRequest = URLRequest(builder: requestBuilder) else {
return Observable.error(RxURLSessionError.requestCreationError)
}
return self.networkOperationQueue.add(dataRequest: urlRequest)
}
R E A C H A B I L I T Y
22
CREATE REACHABILITY SERVICE
class DefaultReachabilityService: ReachabilityService {
private let _reachabilitySubject: BehaviorSubject<ReachabilityStatus>
var reachability: Observable<ReachabilityStatus> {
return _reachabilitySubject.asObservable()
}
let _reachability: Reachability
init() throws {
guard let reachabilityRef = Reachability() else { throw ReachabilityServiceError.failedToCreate }
let reachabilitySubject = BehaviorSubject<ReachabilityStatus>(value: .unreachable)
let backgroundQueue = DispatchQueue(label: "reachability.wificheck")
reachabilityRef.whenReachable = { reachability in
backgroundQueue.async {
reachabilitySubject.on(.next(.reachable(viaWiFi: reachabilityRef.isReachableViaWiFi)))
}
}
reachabilityRef.whenUnreachable = { reachability in
backgroundQueue.async {
reachabilitySubject.on(.next(.unreachable))
}
}
try reachabilityRef.startNotifier()
_reachability = reachabilityRef
_reachabilitySubject = reachabilitySubject
}
}
How we create observable for reachability of network (by Krunoslav Zaher):
R E A C H A B I L I T Y
23
DISPLAY REACHABILITY MESSAGE
reachabilityService.reachability
.skip(1)
.throttle(10, scheduler: MainScheduler.instance)
.observeOn(MainScheduler.instance)
.subscribe(onNext: {
$0.reachable ? self.hideMessage() : self.showMessage(.lostConnection)
}) !!>>> disposeBag
How we subscribe to reachability observable:
B L U E T O O T H
24
SUBSCRIBING TO A BLUETOOTH STREAM
class AwesomeViewController: UIViewController {
let viewModel = DeviceStatusViewModel()
@IBOutlet weak var batteryImageView: UIImageView!
func viewDidLoad() {
bindToViewModel()
}
override func bindToViewModel() {
super.viewDidLoad()
viewModel.devicesManager.batteryStatus
.subscribeOn(MainScheduler.instance)
.subscribe(next: { batteryStatus in
self.batteryImageView.image = self.batteryImageForStatus(batteryStatus)
}) !!>>> rx_diposeBag
}
}
L O O K S G R E A T B U T …
25
STACKTRACE
HELL
RX BEST PRACTICES
B E S T P R A C T I C E S
27
infix operator !=> : Binding
infix operator !!>>> : Binding
public func !=> <T, P: ObserverType>(left: Variable<T>, right: P) !-> Disposable where P.E !== T {
return left.asObservable().bindTo(right)
}
public func !=> (left: UIButton, right: @escaping () !-> Void) !-> Disposable {
return left.rx.tap.subscribe(onNext: { right() })
}
CREATE OPERATORS FOR COMMON TASKS
Syntax sugar that greatly reduces boilerplate code:
B E S T P R A C T I C E S
28
public func !!<-> <T>(property: ControlProperty<T>, variable: Variable<T>) !->
Disposable {
let bindToUIDisposable = variable
.asObservable()
.bindTo(property)
let bindToVariable = property
.subscribe(
onNext: { n in
variable.value = n
},
onCompleted: {
bindToUIDisposable.dispose()
}
)
return Disposables.create(bindToUIDisposable, bindToVariable)
}
TWO-WAY BINDING
S C R O L L V I E W
29
SCROLL VIEW EXTENSIONS (AS PROMISED)
extension UIScrollView {
public var rx_scrolledUnderTop: Observable<Bool> {
return self.rx.contentOffset
.map { $0.y > 0 }
.distinctUntilChanged()
}
public var rx_scrolledUnderBottom: Observable<Bool> {
return self.rx.contentOffset
.map { $0.y < self.contentSize.height - self.frame.size.height - 1 }
.distinctUntilChanged()
}
}
Create extension for scroll view.
B E S T P R A C T I C E S
30
cell.viewOpportunityOverlayView.rx_tapGesture !=> {
self.showOpportunityDetail(opportunityVM.opportunity)
} !!>>> cell.cellDisposeBag
WATCH OUT FOR CELL REUSE
Be sure to reset bindings on cell reuse! In view controller:
override func prepareForReuse() {
super.prepareForReuse()
cellDisposeBag = DisposeBag()
}
In table view cell:
B E S T P R A C T I C E S
31
func bindToViewModel() {
Observable.combineLatest(vm.passwordValid, vm.passwordIsMinLength) {
$0 !&& $1
} !=> passwordReqsLabel.rx_hidden !!>>> rx_disposeBag
vm.emailAddress !<- emailAddressField.rx_text !!>>> rx_disposeBag
vm.password !<- passwordField.rx_text !!>>> rx_disposeBag
vm.passwordConfirmation !<- confirmPasswordField.rx_text !!>>> rx_disposeBag
}
@IBOutlet weak var settingsButton: UIButton! {
didSet {
settingsButton !=> showSettingsVC !!>>> rx_disposeBag
}
}
DESIGNATED METHOD FOR BINDING
B E S T P R A C T I C E S
32
class DeviceManager {
private var batteryStatus = Variable<BatteryLevel>(.low)
public var batteryStatusObs = batteryStatus.asObservable()
}
PUBLIC VS. PRIVATE
B E S T P R A C T I C E S
33
extension ObservableType {
public func ip_repeatingTimeouts(
interval dueTime: RxTimeInterval,
element: E,
scheduler: SchedulerType = MainScheduler.instance
) !-> Observable<E> {
return
Observable.of(
self.asObservable(),
debounce(dueTime, scheduler: scheduler).map { _ in element }
)
.merge()
}
}
REPEATING TIMEOUTS
CONCLUSIONS
35
• What are you reacting to?
• Are you using a struct or a class?
• Observable vs. Variable?
• Does the subscription need to update things on the screen?
• Will the view update while it’s being displayed?
ASK YOURSELF…
C O N C L U S I O N S
36
CLOSING
THOUGHTS
C O N C L U S I O N S
© Christian Howland
37
• RxMarbles.com
• ReactiveX.io
• https://github.com/IntrepidPursuits/swift-wisdom
• https://github.com/ReactiveX/RxSwift
• rxswift.slack.com
USEFUL LINKS
C O N C L U S I O N S
THANKS!

Contenu connexe

Tendances

Joan miro
Joan miroJoan miro
Joan miro
ahcb
 

Tendances (18)

Everything You (N)ever Wanted to Know about Testing View Controllers
Everything You (N)ever Wanted to Know about Testing View ControllersEverything You (N)ever Wanted to Know about Testing View Controllers
Everything You (N)ever Wanted to Know about Testing View Controllers
 
What's new in iOS9
What's new in iOS9What's new in iOS9
What's new in iOS9
 
Introductionandgreetings
IntroductionandgreetingsIntroductionandgreetings
Introductionandgreetings
 
Testing view controllers with Quick and Nimble
Testing view controllers with Quick and NimbleTesting view controllers with Quick and Nimble
Testing view controllers with Quick and Nimble
 
Quick: Better Tests via Incremental Setup
Quick: Better Tests via Incremental SetupQuick: Better Tests via Incremental Setup
Quick: Better Tests via Incremental Setup
 
Swift Delhi: Practical POP
Swift Delhi: Practical POPSwift Delhi: Practical POP
Swift Delhi: Practical POP
 
The rise and fall of a techno DJ, plus more new reviews and notable screenings
The rise and fall of a techno DJ, plus more new reviews and notable screeningsThe rise and fall of a techno DJ, plus more new reviews and notable screenings
The rise and fall of a techno DJ, plus more new reviews and notable screenings
 
Java awt
Java awtJava awt
Java awt
 
KODE JS POKENNNNN
KODE JS POKENNNNNKODE JS POKENNNNN
KODE JS POKENNNNN
 
Android Wear Essentials
Android Wear EssentialsAndroid Wear Essentials
Android Wear Essentials
 
Practical Protocol-Oriented-Programming
Practical Protocol-Oriented-ProgrammingPractical Protocol-Oriented-Programming
Practical Protocol-Oriented-Programming
 
Angular.js is super cool
Angular.js is super coolAngular.js is super cool
Angular.js is super cool
 
Capture image on eye blink
Capture image on eye blinkCapture image on eye blink
Capture image on eye blink
 
Patterns Are Good For Managers
Patterns Are Good For ManagersPatterns Are Good For Managers
Patterns Are Good For Managers
 
The 2016 Android Developer Toolbox [MOBILIZATION]
The 2016 Android Developer Toolbox [MOBILIZATION]The 2016 Android Developer Toolbox [MOBILIZATION]
The 2016 Android Developer Toolbox [MOBILIZATION]
 
Wild Flies and a Camel Java EE Integration Stories
Wild Flies and a Camel Java EE Integration StoriesWild Flies and a Camel Java EE Integration Stories
Wild Flies and a Camel Java EE Integration Stories
 
Joan miro
Joan miroJoan miro
Joan miro
 
The 2016 Android Developer Toolbox [NANTES]
The 2016 Android Developer Toolbox [NANTES]The 2016 Android Developer Toolbox [NANTES]
The 2016 Android Developer Toolbox [NANTES]
 

Similaire à Prescribing RX Responsibly

Redux. From twitter hype to production
Redux. From twitter hype to productionRedux. From twitter hype to production
Redux. From twitter hype to production
FDConf
 
Droidcon2013 android experience lahoda
Droidcon2013 android experience lahodaDroidcon2013 android experience lahoda
Droidcon2013 android experience lahoda
Droidcon Berlin
 

Similaire à Prescribing RX Responsibly (20)

Redux. From twitter hype to production
Redux. From twitter hype to productionRedux. From twitter hype to production
Redux. From twitter hype to production
 
Redux with angular 2 - workshop 2016
Redux with angular 2 - workshop 2016Redux with angular 2 - workshop 2016
Redux with angular 2 - workshop 2016
 
Redux. From twitter hype to production
Redux. From twitter hype to productionRedux. From twitter hype to production
Redux. From twitter hype to production
 
From mvc to redux: 停看聽
From mvc to redux: 停看聽From mvc to redux: 停看聽
From mvc to redux: 停看聽
 
From zero to hero with the reactive extensions for java script
From zero to hero with the reactive extensions for java scriptFrom zero to hero with the reactive extensions for java script
From zero to hero with the reactive extensions for java script
 
From zero to hero with the reactive extensions for JavaScript
From zero to hero with the reactive extensions for JavaScriptFrom zero to hero with the reactive extensions for JavaScript
From zero to hero with the reactive extensions for JavaScript
 
Improving android experience for both users and developers
Improving android experience for both users and developersImproving android experience for both users and developers
Improving android experience for both users and developers
 
Droidcon2013 android experience lahoda
Droidcon2013 android experience lahodaDroidcon2013 android experience lahoda
Droidcon2013 android experience lahoda
 
React state managmenet with Redux
React state managmenet with ReduxReact state managmenet with Redux
React state managmenet with Redux
 
Visualforce: Using JavaScript Remoting for Apex Controllers
Visualforce: Using JavaScript Remoting for Apex ControllersVisualforce: Using JavaScript Remoting for Apex Controllers
Visualforce: Using JavaScript Remoting for Apex Controllers
 
Reactive Programming - ReactFoo 2020 - Aziz Khambati
Reactive Programming - ReactFoo 2020 - Aziz KhambatiReactive Programming - ReactFoo 2020 - Aziz Khambati
Reactive Programming - ReactFoo 2020 - Aziz Khambati
 
Workshop 25: React Native - Components
Workshop 25: React Native - ComponentsWorkshop 25: React Native - Components
Workshop 25: React Native - Components
 
Jsf intro
Jsf introJsf intro
Jsf intro
 
Declarative presentations UIKonf
Declarative presentations UIKonfDeclarative presentations UIKonf
Declarative presentations UIKonf
 
Unidirectional Data Flow in Swift
Unidirectional Data Flow in SwiftUnidirectional Data Flow in Swift
Unidirectional Data Flow in Swift
 
Functional Reactive Programming - RxSwift
Functional Reactive Programming - RxSwiftFunctional Reactive Programming - RxSwift
Functional Reactive Programming - RxSwift
 
RxJS Operators - Real World Use Cases (FULL VERSION)
RxJS Operators - Real World Use Cases (FULL VERSION)RxJS Operators - Real World Use Cases (FULL VERSION)
RxJS Operators - Real World Use Cases (FULL VERSION)
 
Protocol-Oriented MVVM (extended edition)
Protocol-Oriented MVVM (extended edition)Protocol-Oriented MVVM (extended edition)
Protocol-Oriented MVVM (extended edition)
 
[22]Efficient and Testable MVVM pattern
[22]Efficient and Testable MVVM pattern[22]Efficient and Testable MVVM pattern
[22]Efficient and Testable MVVM pattern
 
Cycle.js - A functional reactive UI framework
Cycle.js - A functional reactive UI frameworkCycle.js - A functional reactive UI framework
Cycle.js - A functional reactive UI framework
 

Dernier

TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
mohitmore19
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
masabamasaba
 

Dernier (20)

Chinsurah Escorts ☎️8617697112 Starting From 5K to 15K High Profile Escorts ...
Chinsurah Escorts ☎️8617697112  Starting From 5K to 15K High Profile Escorts ...Chinsurah Escorts ☎️8617697112  Starting From 5K to 15K High Profile Escorts ...
Chinsurah Escorts ☎️8617697112 Starting From 5K to 15K High Profile Escorts ...
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
Define the academic and professional writing..pdf
Define the academic and professional writing..pdfDefine the academic and professional writing..pdf
Define the academic and professional writing..pdf
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) SolutionIntroducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
 
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
10 Trends Likely to Shape Enterprise Technology in 2024
10 Trends Likely to Shape Enterprise Technology in 202410 Trends Likely to Shape Enterprise Technology in 2024
10 Trends Likely to Shape Enterprise Technology in 2024
 
Exploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdfExploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdf
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
 
Generic or specific? Making sensible software design decisions
Generic or specific? Making sensible software design decisionsGeneric or specific? Making sensible software design decisions
Generic or specific? Making sensible software design decisions
 
Architecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastArchitecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the past
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
 
%in Harare+277-882-255-28 abortion pills for sale in Harare
%in Harare+277-882-255-28 abortion pills for sale in Harare%in Harare+277-882-255-28 abortion pills for sale in Harare
%in Harare+277-882-255-28 abortion pills for sale in Harare
 

Prescribing RX Responsibly

  • 2. 2 AGENDA 01RX INTRO 02WHEN TO USE RX (OR NOT) 03RX BEST PRACTICES 04CONCLUSION AND TAKEAWAYS
  • 5. W H A T I S R X ? 5 •RxSwift - Swift implementation of ReactiveX •Follows the “Observer pattern” •Declarative way of defining the data flow in your app •Avoid “callback hell” •Data flow is handled via manageable streams
  • 6. W H A T I S R X ? 6 STREAMS Observable<WaterMolecule> Observable<Bool> Observable<MeetUp> of things. One thing at a time.
  • 7. W H A T I S R X ? 7RxMarbles.com
  • 8. W H A T I S R X ? 8RxMarbles.com
  • 9. W H A T I S R X ? 9 Observable RX ECOSYSTEM Variable Subject PublishSubject Driver DisposeBag BehaviorSubject Observer
  • 10. W H A T I S R X ? 10 Observable RX ECOSYSTEM Variable Subject PublishSubject Driver DisposeBag BehaviorSubject Observer
  • 12. 12 01User actions (button taps, text field delegates) 02Async operations (Network calls, processing) 03Bindings (VC!!<-> VM !!<-> Model) L I S T WHEN TO USE RX 04Prevent code 🍝
  • 13. B U T T O N A C T I O N 13 WITHOUT RX @IBAction func logoTapped(_ sender: UIButton) { dismissUntilHome() } navBar.logoButton !=> dismissUntilHome !!>>> rx_disposeBag WITH RX Drag and drop to create IBAction function. A bit more complicated if it is nested in a custom view. We are using Fira Code font: https://github.com/tonsky/FiraCode
  • 14. D A T E P I C K E R 14 WITH RX WITHOUT RX Drag and drop to create IBAction function. A bit more complicated if it is nested in a custom view, or number of date pickers are not constant. datePicker.rx.date !=> viewModel.endDate !!>>> rx_disposeBag @IBAction func datePicked(_ sender: UIDatePicker) { viewModel.endDate = sender.date }
  • 15. T E X T F I E L D 15 WITH RX titleField.textView.rx.text.orEmpty !!<-> viewModel.title !!>>> rx_disposeBag Create binding in view controller. WITHOUT RX Set up delegate for the text field to listen for edit events to update view model, and manually trigger UI update when view model’s property has changed. func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) var title: String = "" { didSet { updateTextFields() } }
  • 16. SCROLLING UNDER S C R O L L V I E W
  • 17. S C R O L L V I E W 17 WITH RX tableView.rx_scrolledUnderTop !=> viewModel.showTopGradient !!>>> rx_disposeBag tableView.rx_scrolledUnderBottom !=> viewModel.showBottomGradient !!>>> rx_disposeBag Create binding in view controller. WITHOUT RX Set up delegate extensions and do the calculation within the method, at multiple places for multiple classes: func scrollViewDidScroll(_ scrollView: UIScrollView)
  • 18. P A G I N A T I O N 18 SET UP DATA CONTROLLER func getPaginatedData<T: RealmSwift.Object>(resource: Resource, loadNextPageTrigger: Observable<Void>, dataParser: @escaping (Data) !-> ([T], Int)) !-> Observable<[T]> { let existingObjects: [T] = Realm.ip_objects(type: T.self)!?.toArray() !?? [] return recursiveGetPaginatedData(resource: resource, lastModified: lastModifiedDate, dataParser: dataParser, loadedSoFar: [], page: 1, loadNextPageTrigger: loadNextPageTrigger).startWith(existingObjects) } func recursiveGetPaginatedData<T: RealmSwift.Object>(resource: Resource, dataParser: @escaping (Data) !-> ([T], Int), loadedSoFar: [T], page: Int, loadNextPageTrigger: Observable<Void>) !-> Observable<[T]> { guard let urlRequest = URLRequest(builder: URLRequestBuilder(resource: resource, paginationPage: page, authenticationToken = authenticationToken) else { return Observable.just(loadedSoFar) } return networkOperationQueue.add(dataRequest: urlRequest).observeOn(MainScheduler.instance) .flatMap { data !-> Observable<[T]> in var justLoaded = loadedSoFar let (models, paginationTotalItems) = dataParser(data) justLoaded.append(contentsOf: models) if justLoaded.count !== paginationTotalItems { Realm.ip_add(justLoaded, update: true, configuration: self.realmConfiguration) return Observable.just(justLoaded) } return Observable.concat([ Observable.just(justLoaded), Observable.never().takeUntil(loadNextPageTrigger), Observable.deferred { self.recursiveGetPaginatedData(resource: resource, dataParser: dataParser, loadedSoFar: justLoaded, page: page + 1, loadNextPageTrigger: loadNextPageTrigger) } ]) } } Functions of the network call in data controller:
  • 19. P A G I N A T I O N 19 SET UP VIEW MODEL func opportunities(loadNextPageTrigger: Observable<Void>) !-> Observable<[OpportunityModel]> { return getPaginatedData(resource: Resource.opportunities, loadNextPageTrigger: loadNextPageTrigger) { (data) !-> ([OpportunityRealmModel], Int) in let opportunitiesModel = try! OpportunitiesModel(node: data) return (opportunitiesModel.opportunities, opportunitiesModel.total) } .map { $0 as [OpportunityModel] } } Function of the API call in data controller: Where we make the API call in view model: dataController.opportunities(loadNextPageTrigger: nextPageTrigger.asObservable()) .map { $0.map { OpportunityCellViewModel(opportunity: $0) } } .subscribe( onNext: { self.opportunityCellViewModels = $0 self.hasMoreOpportunities = true }, onError: { Logger.error($0) NotificationCenter.postMessage(type: .requestFailure) self.hasMoreOpportunities = false }, onCompleted: { self.opportunityCellViewModels.append(EndOfListViewModel()) self.hasMoreOpportunities = false }) !!>>> rx_disposeBag
  • 20. P A G I N A T I O N 20 GET NEXT PAGE IN VIEW MODEL func nextPage() { nextPageTrigger.fire() } How we get the next page in the view model:
  • 21. N E T W O R K C A L L S 21 CHAINED NETWORK CALLS guard let s3Object = requestS3Object(for: .opportunity) else { return nil } return s3Object.observeOn(MainScheduler.instance).flatMap { s3Object !-> Observable<Bool> in opportunity.imageURL = URL(string: s3Object.publicURL) opportunity.imageKey = s3Object.key guard let presignedURL = URL(string: s3Object.presignedURL) else { return Observable.error(RxURLSessionError.requestCreationError) } return self.uploadImage(data: imageData, to: presignedURL) }.observeOn(MainScheduler.instance).flatMap { imageUploadSuccess !-> Observable<Data> in requestBuilder.data = opportunity.toJson() guard let urlRequest = URLRequest(builder: requestBuilder) else { return Observable.error(RxURLSessionError.requestCreationError) } return self.networkOperationQueue.add(dataRequest: urlRequest) }
  • 22. R E A C H A B I L I T Y 22 CREATE REACHABILITY SERVICE class DefaultReachabilityService: ReachabilityService { private let _reachabilitySubject: BehaviorSubject<ReachabilityStatus> var reachability: Observable<ReachabilityStatus> { return _reachabilitySubject.asObservable() } let _reachability: Reachability init() throws { guard let reachabilityRef = Reachability() else { throw ReachabilityServiceError.failedToCreate } let reachabilitySubject = BehaviorSubject<ReachabilityStatus>(value: .unreachable) let backgroundQueue = DispatchQueue(label: "reachability.wificheck") reachabilityRef.whenReachable = { reachability in backgroundQueue.async { reachabilitySubject.on(.next(.reachable(viaWiFi: reachabilityRef.isReachableViaWiFi))) } } reachabilityRef.whenUnreachable = { reachability in backgroundQueue.async { reachabilitySubject.on(.next(.unreachable)) } } try reachabilityRef.startNotifier() _reachability = reachabilityRef _reachabilitySubject = reachabilitySubject } } How we create observable for reachability of network (by Krunoslav Zaher):
  • 23. R E A C H A B I L I T Y 23 DISPLAY REACHABILITY MESSAGE reachabilityService.reachability .skip(1) .throttle(10, scheduler: MainScheduler.instance) .observeOn(MainScheduler.instance) .subscribe(onNext: { $0.reachable ? self.hideMessage() : self.showMessage(.lostConnection) }) !!>>> disposeBag How we subscribe to reachability observable:
  • 24. B L U E T O O T H 24 SUBSCRIBING TO A BLUETOOTH STREAM class AwesomeViewController: UIViewController { let viewModel = DeviceStatusViewModel() @IBOutlet weak var batteryImageView: UIImageView! func viewDidLoad() { bindToViewModel() } override func bindToViewModel() { super.viewDidLoad() viewModel.devicesManager.batteryStatus .subscribeOn(MainScheduler.instance) .subscribe(next: { batteryStatus in self.batteryImageView.image = self.batteryImageForStatus(batteryStatus) }) !!>>> rx_diposeBag } }
  • 25. L O O K S G R E A T B U T … 25 STACKTRACE HELL
  • 27. B E S T P R A C T I C E S 27 infix operator !=> : Binding infix operator !!>>> : Binding public func !=> <T, P: ObserverType>(left: Variable<T>, right: P) !-> Disposable where P.E !== T { return left.asObservable().bindTo(right) } public func !=> (left: UIButton, right: @escaping () !-> Void) !-> Disposable { return left.rx.tap.subscribe(onNext: { right() }) } CREATE OPERATORS FOR COMMON TASKS Syntax sugar that greatly reduces boilerplate code:
  • 28. B E S T P R A C T I C E S 28 public func !!<-> <T>(property: ControlProperty<T>, variable: Variable<T>) !-> Disposable { let bindToUIDisposable = variable .asObservable() .bindTo(property) let bindToVariable = property .subscribe( onNext: { n in variable.value = n }, onCompleted: { bindToUIDisposable.dispose() } ) return Disposables.create(bindToUIDisposable, bindToVariable) } TWO-WAY BINDING
  • 29. S C R O L L V I E W 29 SCROLL VIEW EXTENSIONS (AS PROMISED) extension UIScrollView { public var rx_scrolledUnderTop: Observable<Bool> { return self.rx.contentOffset .map { $0.y > 0 } .distinctUntilChanged() } public var rx_scrolledUnderBottom: Observable<Bool> { return self.rx.contentOffset .map { $0.y < self.contentSize.height - self.frame.size.height - 1 } .distinctUntilChanged() } } Create extension for scroll view.
  • 30. B E S T P R A C T I C E S 30 cell.viewOpportunityOverlayView.rx_tapGesture !=> { self.showOpportunityDetail(opportunityVM.opportunity) } !!>>> cell.cellDisposeBag WATCH OUT FOR CELL REUSE Be sure to reset bindings on cell reuse! In view controller: override func prepareForReuse() { super.prepareForReuse() cellDisposeBag = DisposeBag() } In table view cell:
  • 31. B E S T P R A C T I C E S 31 func bindToViewModel() { Observable.combineLatest(vm.passwordValid, vm.passwordIsMinLength) { $0 !&& $1 } !=> passwordReqsLabel.rx_hidden !!>>> rx_disposeBag vm.emailAddress !<- emailAddressField.rx_text !!>>> rx_disposeBag vm.password !<- passwordField.rx_text !!>>> rx_disposeBag vm.passwordConfirmation !<- confirmPasswordField.rx_text !!>>> rx_disposeBag } @IBOutlet weak var settingsButton: UIButton! { didSet { settingsButton !=> showSettingsVC !!>>> rx_disposeBag } } DESIGNATED METHOD FOR BINDING
  • 32. B E S T P R A C T I C E S 32 class DeviceManager { private var batteryStatus = Variable<BatteryLevel>(.low) public var batteryStatusObs = batteryStatus.asObservable() } PUBLIC VS. PRIVATE
  • 33. B E S T P R A C T I C E S 33 extension ObservableType { public func ip_repeatingTimeouts( interval dueTime: RxTimeInterval, element: E, scheduler: SchedulerType = MainScheduler.instance ) !-> Observable<E> { return Observable.of( self.asObservable(), debounce(dueTime, scheduler: scheduler).map { _ in element } ) .merge() } } REPEATING TIMEOUTS
  • 35. 35 • What are you reacting to? • Are you using a struct or a class? • Observable vs. Variable? • Does the subscription need to update things on the screen? • Will the view update while it’s being displayed? ASK YOURSELF… C O N C L U S I O N S
  • 36. 36 CLOSING THOUGHTS C O N C L U S I O N S © Christian Howland
  • 37. 37 • RxMarbles.com • ReactiveX.io • https://github.com/IntrepidPursuits/swift-wisdom • https://github.com/ReactiveX/RxSwift • rxswift.slack.com USEFUL LINKS C O N C L U S I O N S