Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

RxSwift 활용하기 - Let'Swift 2017

407 views

Published on

RxSwift 활용하기
Let'Swift 2017 발표

Published in: Technology
  • Be the first to comment

  • Be the first to like this

RxSwift 활용하기 - Let'Swift 2017

  1. 1. letswift(17) RxSwift Reactive Extensions for Swift 최완복
  2. 2. iOS fb.com/wanbok choi@wanbok.com
  3. 3. letswift(17) Prologue
  4. 4. subscribe
  5. 5. : 86 : 47 subscribe - : 40 : 5
  6. 6. letswift(17) RxSwift ?
  7. 7. RxSwift • / • • GCD, KVO, Delegate, NotificationCenter •
  8. 8. Adding numbers • UITextField • KVO : .valueChanged • UITextFieldDelegate : • • UILabel text func textField(UITextField, shouldChangeCharac tersIn: NSRange, replacementString: String) https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/Numbers
  9. 9. Adding numbers Rx • UITextField text • • • UILabel text https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/Numbers
  10. 10. Adding numbers func combineValues() { let num1 = Int(self.number1?.text ?? "") ?? 0 let num2 = Int(self.number2?.text ?? "") ?? 0 let num3 = Int(self.number3?.text ?? "") ?? 0 let result: Int = num1 + num2 + num3 self.result?.text = result.description } override func viewDidLoad() { super.viewDidLoad() number1?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) number2?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) number3?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) } } https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/Numbers
  11. 11. Adding numbers .valueChanged func combineValues() { let num1 = Int(self.number1?.text ?? "") ?? 0 let num2 = Int(self.number2?.text ?? "") ?? 0 let num3 = Int(self.number3?.text ?? "") ?? 0 let result: Int = num1 + num2 + num3 self.result?.text = result.description } override func viewDidLoad() { super.viewDidLoad() number1?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) number2?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) number3?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) } }
  12. 12. Adding numbers func combineValues() { let num1 = Int(self.number1?.text ?? "") ?? 0 let num2 = Int(self.number2?.text ?? "") ?? 0 let num3 = Int(self.number3?.text ?? "") ?? 0 let result: Int = num1 + num2 + num3 self.result?.text = result.description } override func viewDidLoad() { super.viewDidLoad() number1?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) number2?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) number3?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) } }
  13. 13. Adding numbers UILabel text func combineValues() { let num1 = Int(self.number1?.text ?? "") ?? 0 let num2 = Int(self.number2?.text ?? "") ?? 0 let num3 = Int(self.number3?.text ?? "") ?? 0 let result: Int = num1 + num2 + num3 self.result?.text = result.description } override func viewDidLoad() { super.viewDidLoad() number1?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) number2?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) number3?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) } }
  14. 14. Adding numbers override func viewDidLoad() { super.viewDidLoad() Observable .combineLatest( number1.rx.text.orEmpty, number2.rx.text.orEmpty, number3.rx.text.orEmpty ) { text1, text2, text3 -> Int in return (Int(text1) ?? 0) + (Int(text2) ?? 0) + (Int(text3) ?? 0) } .map { $0.description } .bind(to: result.rx.text) .disposed(by: disposeBag) } https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/Numbers
  15. 15. Adding numbers number.rx.text override func viewDidLoad() { super.viewDidLoad() Observable .combineLatest( number1.rx.text.orEmpty, number2.rx.text.orEmpty, number3.rx.text.orEmpty ) { text1, text2, text3 -> Int in return (Int(text1) ?? 0) + (Int(text2) ?? 0) + (Int(text3) ?? 0) } .map { $0.description } .bind(to: result.rx.text) .disposed(by: disposeBag) }
  16. 16. Adding numbers override func viewDidLoad() { super.viewDidLoad() Observable .combineLatest( number1.rx.text.orEmpty, number2.rx.text.orEmpty, number3.rx.text.orEmpty ) { text1, text2, text3 -> Int in return (Int(text1) ?? 0) + (Int(text2) ?? 0) + (Int(text3) ?? 0) } .map { $0.description } .bind(to: result.rx.text) .disposed(by: disposeBag) }
  17. 17. Adding numbers UILabel.rx.text override func viewDidLoad() { super.viewDidLoad() Observable .combineLatest( number1.rx.text.orEmpty, number2.rx.text.orEmpty, number3.rx.text.orEmpty ) { text1, text2, text3 -> Int in return (Int(text1) ?? 0) + (Int(text2) ?? 0) + (Int(text3) ?? 0) } .map { $0.description } .bind(to: result.rx.text) .disposed(by: disposeBag) }
  18. 18. func combineValues() { let num1 = Int(self.number1?.text ?? "") ?? 0 let num2 = Int(self.number2?.text ?? "") ?? 0 let num3 = Int(self.number3?.text ?? "") ?? 0 let result: Int = num1 + num2 + num3 self.result?.text = result.description } override func viewDidLoad() { super.viewDidLoad() number1?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) number2?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) number3?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) } } override func viewDidLoad() { super.viewDidLoad() Observable .combineLatest( number1.rx.text.orEmpty, number2.rx.text.orEmpty, number3.rx.text.orEmpty ) { text1, text2, text3 -> Int in return (Int(text1) ?? 0) + (Int(text2) ?? 0) + (Int(text3) ?? 0) } .map { $0.description } .bind(to: result.rx.text) .disposed(by: disposeBag) }
  19. 19. func combineValues() { let num1 = Int(self.number1?.text ?? "") ?? 0 let num2 = Int(self.number2?.text ?? "") ?? 0 let num3 = Int(self.number3?.text ?? "") ?? 0 let result: Int = num1 + num2 + num3 self.result?.text = result.description } override func viewDidLoad() { super.viewDidLoad() number1?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) number2?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) number3?.addTarget(self, action: #selector(self.combineValues), for: .valueChanged) } } override func viewDidLoad() { super.viewDidLoad() Observable .combineLatest( number1.rx.text.orEmpty, number2.rx.text.orEmpty, number3.rx.text.orEmpty ) { text1, text2, text3 -> Int in return (Int(text1) ?? 0) + (Int(text2) ?? 0) + (Int(text3) ?? 0) } .map { $0.description } .bind(to: result.rx.text) .disposed(by: disposeBag) }
  20. 20. func combineValues() { let num1 = Int(self.number1?.text ?? "") ?? 0 let num2 = Int(self.number2?.text ?? "") ?? 0 let num3 = Int(self.number3?.text ?? "") ?? 0 let result: Int = num1 + num2 + num3 self.result?.text = result.description } override func viewDidLoad() { super.viewDidLoad() number1?.addTarget(self, action: #selector(self.handleNumber1), for: .valueChanged) number2?.addTarget(self, action: #selector(self.handleNumber2), for: .valueChanged) number3?.addTarget(self, action: #selector(self.handleNumber3), for: .valueChanged) } func handleNumber1() { self.analytics1() self.combineValue() } } override func viewDidLoad() { super.viewDidLoad() Observable .combineLatest( number1.rx.text.orEmpty .do(onNext: { analytics1() }), number2.rx.text.orEmpty number3.rx.text.orEmpty ) { text1, text2, text3 -> Int in return (Int(text1) ?? 0) + (Int(text2) ?? 0) + (Int(text3) ?? 0) } .map { $0.description } .bind(to: result.rx.text) .disposed(by: disposeBag) }
  21. 21. func combineValues() { let num1 = Int(self.number1?.text ?? "") ?? 0 let num2 = Int(self.number2?.text ?? "") ?? 0 let num3 = Int(self.number3?.text ?? "") ?? 0 let result: Int = num1 + num2 + num3 self.result?.text = result.description } override func viewDidLoad() { super.viewDidLoad() number1?.addTarget(self, action: #selector(self.handleNumber1), for: .valueChanged) number2?.addTarget(self, action: #selector(self.handleNumber2), for: .valueChanged) number3?.addTarget(self, action: #selector(self.handleNumber3), for: .valueChanged) } func handleNumber1() { self.analytics1() self.combineValue() } … } override func viewDidLoad() { super.viewDidLoad() Observable .combineLatest( number1.rx.text.orEmpty .do(onNext: { analytics1() }), number2.rx.text.orEmpty .do(onNext: { analytics2() }), number3.rx.text.orEmpty .do(onNext: { analytics3() }) ) { text1, text2, text3 -> Int in return (Int(text1) ?? 0) + (Int(text2) ?? 0) + (Int(text3) ?? 0) } .map { $0.description } .bind(to: result.rx.text) .disposed(by: disposeBag) }
  22. 22. func combineValues() { let num1 = Int(self.number1?.text ?? "") ?? 0 let num2 = Int(self.number2?.text ?? "") ?? 0 let num3 = Int(self.number3?.text ?? "") ?? 0 let result: Int = num1 + num2 + num3 self.analyticsTotal(result) self.result?.text = result.description } override func viewDidLoad() { super.viewDidLoad() number1?.addTarget(self, action: #selector(self.handleNumber1), for: .valueChanged) number2?.addTarget(self, action: #selector(self.handleNumber2), for: .valueChanged) number3?.addTarget(self, action: #selector(self.handleNumber3), for: .valueChanged) } func handleNumber1() { self.analytics1() self.combineValue() } … } override func viewDidLoad() { super.viewDidLoad() Observable .combineLatest( number1.rx.text.orEmpty .do(onNext: { analytics1() }), number2.rx.text.orEmpty .do(onNext: { analytics2() }), number3.rx.text.orEmpty .do(onNext: { analytics3() }) ) { text1, text2, text3 -> Int in return (Int(text1) ?? 0) + (Int(text2) ?? 0) + (Int(text3) ?? 0) } .map { $0.description } .do(onNext: { analyticsTotal() }) .bind(to: result.rx.text) .disposed(by: disposeBag) }
  23. 23. func combineValues() { let num1 = Int(self.number1?.text ?? "") ?? 0 let num2 = Int(self.number2?.text ?? "") ?? 0 let num3 = Int(self.number3?.text ?? "") ?? 0 let result: Int = num1 + num2 + num3 self.analyticsTotal(result) self.result?.text = result.description } override func viewDidLoad() { super.viewDidLoad() number1?.addTarget(self, action: #selector(self.handleNumber1), for: .valueChanged) number2?.addTarget(self, action: #selector(self.handleNumber2), for: .valueChanged) number3?.addTarget(self, action: #selector(self.handleNumber3), for: .valueChanged) } func handleNumber1() { self.analytics1() self.combineValue() } … } override func viewDidLoad() { super.viewDidLoad() Observable .combineLatest( number1.rx.text.orEmpty .do(onNext: { analytics1() }), number2.rx.text.orEmpty .do(onNext: { analytics2() }), number3.rx.text.orEmpty .do(onNext: { analytics3() }) ) { text1, text2, text3 -> Int in return (Int(text1) ?? 0) + (Int(text2) ?? 0) + (Int(text3) ?? 0) } .debounce(3) .map { $0.description } .do(onNext: { analyticsTotal() }) .bind(to: result.rx.text) .disposed(by: disposeBag) }
  24. 24. func combineValues() { let num1 = Int(self.number1?.text ?? "") ?? 0 let num2 = Int(self.number2?.text ?? "") ?? 0 let num3 = Int(self.number3?.text ?? "") ?? 0 let result: Int = num1 + num2 + num3 self.analyticsTotal(result) self.result?.text = result.description } override func viewDidLoad() { super.viewDidLoad() number1?.addTarget(self, action: #selector(self.handleNumber1), for: .valueChanged) number2?.addTarget(self, action: #selector(self.handleNumber2), for: .valueChanged) number3?.addTarget(self, action: #selector(self.handleNumber3), for: .valueChanged) } func handleNumber1() { self.analytics1() self.combineValue() } … } override func viewDidLoad() { super.viewDidLoad() Observable .combineLatest( number1.rx.text.orEmpty .do(onNext: { analytics1() }), number2.rx.text.orEmpty .do(onNext: { analytics2() }), number3.rx.text.orEmpty .do(onNext: { analytics3() }) ) { text1, text2, text3 -> Int in return (Int(text1) ?? 0) + (Int(text2) ?? 0) + (Int(text3) ?? 0) } .debounce(3) .map { $0.description } .do(onNext: { analyticsTotal() }) .bind(to: result.rx.text) .disposed(by: disposeBag) } ?
  25. 25. letswift(17) RxSwift ?
  26. 26. RxSwift • • •
  27. 27. UIKit RxOperators MVC ReactorKit, MVVM
  28. 28. ? ?? ?
  29. 29. letswift(17) Observable(with RxMarble)
  30. 30. Observable enum Event<Element> { case next(Element) // next element of a sequence case error(Swift.Error) // sequence failed with error case completed // sequence terminated successfully } class Observable<Element> { func subscribe(_ observer: Observer<Element>) -> Disposable } protocol ObserverType { func on(_ event: Event<Element>) }
  31. 31. Observable import UIKit import RxSwift import RxCocoa class NumbersViewController: ViewController { @IBOutlet weak var number1: UITextField! @IBOutlet weak var number2: UITextField! @IBOutlet weak var number3: UITextField! @IBOutlet weak var result: UILabel! override func viewDidLoad() { super.viewDidLoad() Observable .combineLatest( number1.rx.text.orEmpty, number2.rx.text.orEmpty, number3.rx.text.orEmpty ) { textValue1, textValue2, textValue3 -> Int in return (Int(textValue1) ?? 0) + (Int(textValue2) ?? 0) + (Int(textValue3) ?? 0) } .map { $0.description } .bind(to: result.rx.text) .disposed(by: disposeBag) } } https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/Numbers
  32. 32. Observable .combineLatest { Int($0) + … } “1” “3” “2” 6 number1.rx.text.orEmpty number2.rx.text.orEmpty number3.rx.text.orEmpty
  33. 33. Observable .combineLatest { Int($0) + … } 6 .map { $0.description } “6”
  34. 34. Observable .combineLatest { Int($0) + … } “1” “3” “2” 6 “6” 10
  35. 35. Observable .combineLatest { Int($0) + … } “1” “3” “2” 6 “6” 10 “” 7
  36. 36. letswift(17) Observable
  37. 37. ImagePickerController https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/ImagePicker
  38. 38. ImagePickerController cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  39. 39. ImagePickerController cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  40. 40. ImagePickerController cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  41. 41. ImagePickerController cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  42. 42. ImagePickerController cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  43. 43. ImagePickerController cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  44. 44. ImagePickerController cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  45. 45. ImagePickerController cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  46. 46. ImagePickerController cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  47. 47. ImagePickerController cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  48. 48. extension Reactive where Base: UIImagePickerController { static func createWithParent( _ parent: UIViewController?, animated: Bool = true, configureImagePicker: @escaping (UIImagePickerController) throws -> () = { x in } ) -> Observable<UIImagePickerController> { return Observable.create { [weak parent] observer in let imagePicker = UIImagePickerController() let dismissDisposable = … do { try configureImagePicker(imagePicker) } catch let error { observer.on(.error(error)) return Disposables.create() } guard let parent = parent else { observer.on(.completed) return Disposables.create() } parent.present(imagePicker, animated: animated, completion: nil) observer.on(.next(imagePicker)) return Disposables.create(dismissDisposable, Disposables.create { dismissViewController(imagePicker, animated: animated) }) } } }
  49. 49. extension Reactive where Base: UIImagePickerController { static func createWithParent( _ parent: UIViewController?, animated: Bool = true, configureImagePicker: @escaping (UIImagePickerController) throws -> () = { x in } ) -> Observable<UIImagePickerController> { return Observable.create { [weak parent] observer in let imagePicker = UIImagePickerController() let dismissDisposable = … do { try configureImagePicker(imagePicker) } catch let error { observer.on(.error(error)) return Disposables.create() } guard let parent = parent else { observer.on(.completed) return Disposables.create() } parent.present(imagePicker, animated: animated, completion: nil) observer.on(.next(imagePicker)) return Disposables.create(dismissDisposable, Disposables.create { dismissViewController(imagePicker, animated: animated) }) } } }
  50. 50. ImagePickerController cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  51. 51. extension Reactive where Base: UIImagePickerController { /** Reactive wrapper for `delegate` message. */ public var didFinishPickingMediaWithInfo: Observable<[String : AnyObject]> { return delegate .methodInvoked(#selector(UIImagePickerControllerDelegate.imagePickerController(_:didFin ishPickingMediaWithInfo:))) .map({ (a) in return try castOrThrow(Dictionary<String, AnyObject>.self, a[1]) }) } /** Reactive wrapper for `delegate` message. */ public var didCancel: Observable<()> { return delegate .methodInvoked(#selector(UIImagePickerControllerDelegate.imagePickerControllerDidCancel (_:))) .map {_ in () } } }
  52. 52. cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  53. 53. cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  54. 54. cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  55. 55. letswift(17) UIBindingObserver, ControlEvent, ControlProperty
  56. 56. UIBindingObserver • setter • error • Mainthread binding
  57. 57. extension Reactive where Base: UIImageView { public var image: UIBindingObserver<Base, UIImage?> { return image(transitionType: nil) } public func image(transitionType: String? = nil) -> UIBindingObserver<Base, UIImage?> { return UIBindingObserver(UIElement: base) { imageView, image in … imageView.image = image } } }
  58. 58. public final class UIBindingObserver<UIElementType, Value> : ObserverType where UIElementType: AnyObject { public typealias E = Value weak var UIElement: UIElementType? let binding: (UIElementType, Value) -> Void /// Initializes `ViewBindingObserver` using public init(UIElement: UIElementType, binding: @escaping (UIElementType, Value) -> Void) { self.UIElement = UIElement self.binding = binding } /// Binds next element to owner view as described in `binding`. public func on(_ event: Event<Value>) { if !DispatchQueue.isMain { DispatchQueue.main.async { self.on(event) } return } switch event { case .next(let element): if let view = self.UIElement { binding(view, element) } case .error(let error): bindingErrorToInterface(error) case .completed: break } } }
  59. 59. public final class UIBindingObserver<UIElementType, Value> : ObserverType where UIElementType: AnyObject { public typealias E = Value weak var UIElement: UIElementType? let binding: (UIElementType, Value) -> Void /// Initializes `ViewBindingObserver` using public init(UIElement: UIElementType, binding: @escaping (UIElementType, Value) -> Void) { self.UIElement = UIElement self.binding = binding } /// Binds next element to owner view as described in `binding`. public func on(_ event: Event<Value>) { if !DispatchQueue.isMain { DispatchQueue.main.async { self.on(event) } return } switch event { case .next(let element): if let view = self.UIElement { binding(view, element) } case .error(let error): bindingErrorToInterface(error) case .completed: break } } }
  60. 60. ControlEvent • getter • , • completed • Mainthread
  61. 61. extension Reactive where Base: UIButton { public var tap: ControlEvent<Void> { return controlEvent(.touchUpInside) } } extension Reactive where Base: UIControl { public func controlEvent(_ controlEvents: UIControlEvents) -> ControlEvent<Void> { let source: Observable<Void> = Observable.create { [weak control = self.base] observer in MainScheduler.ensureExecutingOnScheduler() guard let control = control else { observer.on(.completed) return Disposables.create() } let controlTarget = ControlTarget(control: control, controlEvents: controlEvents) { control in observer.on(.next()) } return Disposables.create(with: controlTarget.dispose) }.takeUntil(deallocated) return ControlEvent(events: source) }
  62. 62. public struct ControlEvent<PropertyType> : ControlEventType { public typealias E = PropertyType let _events: Observable<PropertyType> /// Initializes control event with a observable sequence that represents events. /// /// - parameter events: Observable sequence that represents events. /// - returns: Control event created with a observable sequence of events. public init<Ev: ObservableType>(events: Ev) where Ev.E == E { _events = events.subscribeOn(ConcurrentMainScheduler.instance) } /// Subscribes an observer to control events. /// /// - parameter observer: Observer to subscribe to events. /// - returns: Disposable object that can be used to unsubscribe the observer from receiving control events. public func subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == E { return _events.subscribe(observer) } /// - returns: `Observable` interface. public func asObservable() -> Observable<E> { return _events } /// - returns: `ControlEvent` interface. public func asControlEvent() -> ControlEvent<E> { return self } }
  63. 63. public struct ControlEvent<PropertyType> : ControlEventType { public typealias E = PropertyType let _events: Observable<PropertyType> /// Initializes control event with a observable sequence that represents events. /// /// - parameter events: Observable sequence that represents events. /// - returns: Control event created with a observable sequence of events. public init<Ev: ObservableType>(events: Ev) where Ev.E == E { _events = events.subscribeOn(ConcurrentMainScheduler.instance) } /// Subscribes an observer to control events. /// /// - parameter observer: Observer to subscribe to events. /// - returns: Disposable object that can be used to unsubscribe the observer from receiving control events. public func subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == E { return _events.subscribe(observer) } /// - returns: `Observable` interface. public func asObservable() -> Observable<E> { return _events } /// - returns: `ControlEvent` interface. public func asControlEvent() -> ControlEvent<E> { return self } }
  64. 64. ControlProperty • setter + getter • a.k.a. UIBindingObserver + Observable • element shareReplay(1)
  65. 65. extension Reactive where Base: LikeButton { var isLiked: ControlProperty<Bool> { let source: Observable<Bool> = Observable.deferred { [weak button = self.base] in guard let isSelected = button?.isSelected else { return .empty() } return Observable.just(isSelected) } let bindingObserver = UIBindingObserver(UIElement: self.base) { (button, isSelected: Bool) in button.isSelected = isSelected button.tintColor = isSelected ? .colorSelected : .colorNormal } return ControlProperty(values: source, valueSink: bindingObserver) } }
  66. 66. public struct ControlProperty<PropertyType> : ControlPropertyType { public typealias E = PropertyType let _values: Observable<PropertyType> let _valueSink: AnyObserver<PropertyType> public init<V: ObservableType, S: ObserverType>(values: V, valueSink: S) where E == V.E, E == S.E { _values = values.subscribeOn(ConcurrentMainScheduler.instance) _valueSink = valueSink.asObserver() } public func subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == E { return _values.subscribe(observer) } public var changed: ControlEvent<PropertyType> { get { return ControlEvent(events: _values.skip(1)) } } … public func on(_ event: Event<E>) { switch event { case .error(let error): bindingErrorToInterface(error) case .next: _valueSink.on(event) case .completed: _valueSink.on(event) } } }
  67. 67. public struct ControlProperty<PropertyType> : ControlPropertyType { public typealias E = PropertyType let _values: Observable<PropertyType> let _valueSink: AnyObserver<PropertyType> public init<V: ObservableType, S: ObserverType>(values: V, valueSink: S) where E == V.E, E == S.E { _values = values.subscribeOn(ConcurrentMainScheduler.instance) _valueSink = valueSink.asObserver() } public func subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == E { return _values.subscribe(observer) } public var changed: ControlEvent<PropertyType> { get { return ControlEvent(events: _values.skip(1)) } } … public func on(_ event: Event<E>) { switch event { case .error(let error): bindingErrorToInterface(error) case .next: _valueSink.on(event) case .completed: _valueSink.on(event) } } }
  68. 68. letswift(17) Disposing
  69. 69. Disposing let scheduler = SerialDispatchQueueScheduler(qos: .default) let subscription = Observable<Int>.interval(0.3, scheduler: scheduler) .subscribe { event in print(event) } Thread.sleep(forTimeInterval: 2.0) subscription.dispose()
  70. 70. Disposing cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  71. 71. Disposing cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  72. 72. Disposing cameraButton.rx.tap .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } .flatMap { $0.rx.didFinishPickingMediaWithInfo } .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  73. 73. Disposing extension Reactive where Base: UIButton { public var tap: ControlEvent<Void> { return controlEvent(.touchUpInside) } } extension Reactive where Base: UIControl { public func controlEvent(_ controlEvents: UIControlEvents) -> ControlEvent<Void> { let source: Observable<Void> = Observable.create { [weak control = self.base] observer in MainScheduler.ensureExecutingOnScheduler() guard let control = control else { observer.on(.completed) return Disposables.create() } let controlTarget = ControlTarget(control: control, controlEvents: controlEvents) { control in observer.on(.next()) } return Disposables.create(with: controlTarget.dispose) }.takeUntil(deallocated) return ControlEvent(events: source) }
  74. 74. letswift(17) Debugging
  75. 75. Debugging cameraButton.rx.tap.debug(“Tap the button”) .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false }.debug(“Create with parent”) .flatMap { $0.rx.didFinishPickingMediaWithInfo.debug(“mediaWithInfo”) } .debug(“mediaWithInfo out of flatMap”) .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage }.debug(“image”) .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  76. 76. Debugging cameraButton.rx.tap.debug(“Tap the button”) .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false }.debug(“Create with parent”) .flatMap { $0.rx.didFinishPickingMediaWithInfo.debug(“mediaWithInfo”) } .debug(“mediaWithInfo out of flatMap”) .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage }.debug(“image”) .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  77. 77. Debugging cameraButton.rx.tap.debug(“Tap the button”) .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false }.debug(“Create with parent”) .flatMap { $0.rx.didFinishPickingMediaWithInfo.debug(“mediaWithInfo”) } .debug(“mediaWithInfo out of flatMap”) .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage }.debug(“image”) .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  78. 78. Debugging cameraButton.rx.tap.debug(“Tap the button”) .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false }.debug(“Create with parent”) .flatMap { $0.rx.didFinishPickingMediaWithInfo.debug(“mediaWithInfo”) } .debug(“mediaWithInfo out of flatMap”) .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage }.debug(“image”) .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  79. 79. Debugging cameraButton.rx.tap.debug(“Tap the button”) .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false }.debug(“Create with parent”) .flatMap { $0.rx.didFinishPickingMediaWithInfo.debug(“mediaWithInfo”) } .debug(“mediaWithInfo out of flatMap”) .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage }.debug(“image”) .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  80. 80. Debugging cameraButton.rx.tap.debug(“Tap the button”) .flatMapLatest { [weak self] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false }.debug(“Create with parent”) .flatMap { $0.rx.didFinishPickingMediaWithInfo.debug(“mediaWithInfo”) } .debug(“mediaWithInfo out of flatMap”) .take(1) } .map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage }.debug(“image”) .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  81. 81. Debugging // 2017-09-23 10:19:37.986: image -> subscribed 2017-09-23 10:19:37.987: Tap the button -> subscribed // 2017-09-23 10:19:39.433: Tap the button -> Event next(()) 2017-09-23 10:19:39.434: mediaWithInfo out of flatMap -> subscribed 2017-09-23 10:19:39.434: Create with parent -> subscribed 2017-09-23 10:19:39.684: Create with parent -> Event next(UIImagePickerController) 2017-09-23 10:19:39.685: mediaWithInfo -> subscribed 2017-09-23 10:19:45.525: mediaWithInfo -> Event next([“…”]) 2017-09-23 10:19:45.525: image -> Event next(UIImage) 2017-09-23 10:19:45.525: mediaWithInfo out of flatMap -> isDisposed 2017-09-23 10:19:45.525: Create with parent -> isDisposed 2017-09-23 10:19:45.526: mediaWithInfo -> isDisposed
  82. 82. letswift(17)
  83. 83. flatMap vs. withLatestFrom let buttonTap = Observable.just(Void()) let flag = Observable<Int>.interval(10, scheduler: scheduler) .map { _ in rand() % 2 == 0 } .startWith(true) buttonTap.flatMap { _ in flag } .debug("flatMap") .subscribe() buttonTap.withLatestFrom(flag) .debug("withLatestFrom") .subscribe() 2017-09-23 14:27:03.788: flatMap -> subscribed 2017-09-23 14:27:03.793: flatMap -> Event next(true) 2017-09-23 14:27:03.795: withLatestFrom -> subscribed 2017-09-23 14:27:03.796: withLatestFrom -> Event next(true) 2017-09-23 14:27:03.796: withLatestFrom -> Event completed 2017-09-23 14:27:03.796: withLatestFrom -> isDisposed 2017-09-23 14:27:13.793: flatMap -> Event next(false) 2017-09-23 14:27:23.794: flatMap -> Event next(true)
  84. 84. .just() and .share() let observable = Observable.just("Hello, RxSwift!").share() observable .debug("Observable") .subscribe() observable .debug("Observable2") .subscribe() 2017-09-23 13:56:48.347: Observable -> subscribed 2017-09-23 13:56:48.349: Observable -> Event next(Hello, RxSwift!) 2017-09-23 13:56:48.350: Observable -> Event completed 2017-09-23 13:56:48.350: Observable -> isDisposed 2017-09-23 13:56:48.352: Observable2 -> subscribed 2017-09-23 13:56:48.353: Observable2 -> Event completed 2017-09-23 13:56:48.353: Observable2 -> isDisposed
  85. 85. subscribe
  86. 86. letswift(17)

×