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

RxSwift

 RxSwift

Introduction to Asynchronous programming with observable streams for Swift and Cocoa

Stefan Scheidt

September 19, 2016
Tweet

More Decks by Stefan Scheidt

Other Decks in Programming

Transcript

  1. Reactive Extentions History • 2009: Rx.NET • 2010: RxJS •

    2012: RxJava, RxCpp, RxRuby • 2013: RxScala, RxClojure, RxKotlin, RxPY ... (ReactiveCocoa) • 2015: RxSwift
  2. RxSwift Fact Sheet • Swift Support: 2.3, 3.0 (Beta) •

    Code Quality: see CocoaPods • Contributors & Activity: see GitHub • External Dependencies: Non • Usage via CocoaPods, Carthage, Git Submodules • Community Extentions
  3. Observable Sequences: Push vs. Pull 1 • Observable<Element> eq. SequenceType

    • ObservableType.subscribe • eq. SequenceType.generate • vs. generator.next • But can also receive elements asynchronously! 1 RxSwift Getting Started
  4. Events next* (error | completed)? • Sequences can have 0

    or more elements • Once an error or completed event is received, the sequence cannot produce any other element
  5. Examples2 Observable.of(1, 2, 3) .subscribe { print($0) } will print

    next(1) next(2) next(3) completed 2 using RxSwift 3.0.0-beta.1
  6. Disposing release all allocated resources: Observable.of(1, 2 ,3) .subscribe {

    print($0) } .dispose() or better let disposeBag = DisposeBag() Observable.of(1, 2 ,3) .subscribe { print($0) } .addDisposableTo(disposeBag)
  7. Operators 3 • Creating • Transforming • Filtering • Combining

    3 https://github.com/ReactiveX/RxSwift/blob/master/Documentation/API.md
  8. Sequence Generation When does an Observable begin emitting its items?4

    "Cold" Observable: waits until an observer subscribes "Hot" Observable: may begin emitting items as soon as created 4 See also Hot and Cold Observables
  9. Cold Observables let disposeBag = DisposeBag() let observable = Observable.of(1,

    2) observable.subscribe { print($0) }.addDisposableTo(disposeBag) observable.subscribe { print($0) }.addDisposableTo(disposeBag) will print next(1) next(2) completed next(1) next(2) completed
  10. "Hot" Observables let pub = PublishSubject<Int>() pub.onNext(1) let sub1 =

    pub.subscribe { print("sub1: \($0)") } pub.onNext(2) let sub2 = pub.subscribe { print("sub2: \($0)") } pub.onNext(3) sub2.dispose() pub.onCompleted() will print sub1: next(2) sub1: next(3) sub2: next(3) sub1: completed
  11. Create Observables func interval(_ interval: TimeInterval) -> Observable<Int> { return

    Observable.create { observer in print("Subscribed") let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global()) timer.scheduleRepeating(deadline: DispatchTime.now() + interval, interval: interval) let cancel = Disposables.create { print("Disposed") timer.cancel() } var count = 0 timer.setEventHandler { if cancel.isDisposed { return } observer.on(.next(count)) count += 1 } timer.resume() return cancel } }
  12. Cold Observables - again let counter = interval(0.1) let sub1

    = counter.subscribe(onNext: { print("Sub1: \($0)") }) let sub2 = counter.subscribe(onNext: { print("Sub2: \($0)") }) Thread.sleep(forTimeInterval: 0.3) sub1.dispose() Thread.sleep(forTimeInterval: 0.3) sub2.dispose() will print ...
  13. Cold Observables - again Subscribed Subscribed Sub1: 0 Sub2: 0

    Sub1: 1 Sub2: 1 Sub1: 2 Sub2: 2 Disposed Sub2: 3 Sub2: 4 Disposed
  14. Share Replay let counter = interval(0.1).shareReplay(1) let sub1 = counter.subscribe(onNext:

    { print("Sub1: \($0)") }) let sub2 = counter.subscribe(onNext: { print("Sub2: \($0)") }) Thread.sleep(forTimeInterval: 0.3) sub1.dispose() Thread.sleep(forTimeInterval: 0.3) sub2.dispose() will print ...
  15. Share Replay Subscribed Sub1: 0 Sub2: 0 Sub1: 1 Sub2:

    1 Sub1: 2 Sub2: 2 Sub2: 3 Sub2: 4 Sub2: 5 Disposed
  16. Schedulers5 Schedulers abstract away the mechanism for performing work •

    CurrentThreadScheduler: serial on current thread • MainScheduler: serial on main thread • SerialDispatchQueueScheduler: serial on dispatch queue • ConcurrentDispatchQueueScheduler: concurrent on dispatch queue • OperationQueueScheduler: concurrent on operation queue 5 https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Schedulers.md
  17. Observe On Scheduler sequence .observeOn(backgroundScheduler) .map { _ in print("This

    is performed on backgroundScheduler") } .observeOn(MainScheduler.instance) .map { _ in print("This is performed on the main thread") }
  18. Observe On Scheduler let scheduler = SerialDispatchQueueScheduler(internalSerialQueueName: "com.rewe-digital.rxswift.interval") let subscription

    = Observable<Int>.interval(0.3, scheduler: scheduler) .map { "Simply \($0)"} .subscribe(onNext: { print($0) }) Thread.sleep(forTimeInterval: 1.0) subscription.dispose() will print Simply 0 Simply 1 Simply 2
  19. Debugging let scheduler = SerialDispatchQueueScheduler(internalSerialQueueName: "com.rewe-digital.rxswift.interval") let subscription = Observable<Int>.interval(0.3,

    scheduler: scheduler) .debug("debugging ...") .map { "Simply \($0)"} .subscribe(onNext: { print($0) }) Thread.sleep(forTimeInterval: 1.0) subscription.dispose()
  20. Debugging will print 2016-09-09 16:42:29.871: debugging ... -> subscribed 2016-09-09

    16:42:30.172: debugging ... -> Event next(0) Simply 0 2016-09-09 16:42:30.475: debugging ... -> Event next(1) Simply 1 2016-09-09 16:42:30.775: debugging ... -> Event next(2) Simply 2 2016-09-09 16:42:30.873: debugging ... -> disposed
  21. Units Important properties when writing Cocoa/UIKit applications: • Subscribe to

    properties, events on main thread • Observe on main thread • Share events • Don't error out
  22. Units Units6 are convenient wrapper around observables for writing UI

    code • ControlProperty • ControlEvent • Driver 6 https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Units.md
  23. Why Units? let results = query.rx.text .throttle(0.3, scheduler: MainScheduler.instance) .flatMapLatest

    { fetchItems($0) } results.map { "\($0.count)" } .bindTo(resultCount.rx.text) .addDisposableTo(disposeBag) results.bindTo(tableView.rx.itemsWithCellIdentifier("Cell")) { (_, result, cell) in cell.textLabel?.text = "\(result)" }.addDisposableTo(disposeBag)
  24. Problems with this code ... • If fetchItems errors out,

    everything would unbind • If it returns on some background thread, results would be bound to UI there • Results are bound to two UI elements, so two HTTP requests would be made
  25. So ... let results = query.rx.text .throttle(0.3, scheduler: MainScheduler.instance) .flatMapLatest

    { fetchItems($0) .observeOn(MainScheduler.instance) .catchErrorJustReturn([]) }.shareReplay(1) results.map { "\($0.count)" } .bindTo(resultCount.rx.text) .addDisposableTo(disposeBag) results.bindTo(tableView.rx.itemsWithCellIdentifier("Cell")) { (_, result, cell) in cell.textLabel?.text = "\(result)" }.addDisposableTo(disposeBag)
  26. Therefore: Driver let results = query.rx.text.asDriver() .throttle(0.3, scheduler: MainScheduler.instance) .flatMapLatest

    { fetchItems($0) .asDriver(onErrorJustReturn: []) } results.map { "\($0.count)" } .drive(resultCount.rx.text) .addDisposableTo(disposeBag) results.drive(tableView.rx.itemsWithCellIdentifier("Cell")) { (_, result, cell) in cell.textLabel?.text = "\(result)" }.addDisposableTo(disposeBag)
  27. By the way ... Observable Sequences are Monads7 7 Swift

    Functors, Applicatives, and Monads in Pictures