letswift(17)
RxSwift
Reactive Extensions for Swift
최완복
iOS 

fb.com/wanbok

choi@wanbok.com
letswift(17)
Prologue
subscribe
: 86 : 47
subscribe
- : 40 : 5
letswift(17)
RxSwift ?
RxSwift
• / 

• 

• GCD, KVO, Delegate, NotificationCenter

•
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
Adding numbers
Rx 

• UITextField text 

• 

• 

• UILabel text
https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/Numbers
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
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)
}
}
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)
}
}
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)
}
}
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
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
?
letswift(17)
RxSwift ?
RxSwift
• 

• 

•
UIKit RxOperators
MVC ReactorKit, MVVM
? ??

?
letswift(17)
Observable(with RxMarble)
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>)
}
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
Observable
.combineLatest { Int($0) + … }
“1”
“3”
“2”
6
number1.rx.text.orEmpty
number2.rx.text.orEmpty
number3.rx.text.orEmpty
Observable
.combineLatest { Int($0) + … }
6
.map { $0.description }
“6”
Observable
.combineLatest { Int($0) + … }
“1”
“3”
“2”
6
“6”
10
Observable
.combineLatest { Int($0) + … }
“1”
“3”
“2”
6
“6”
10
“”
7
letswift(17)
Observable
ImagePickerController
https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/ImagePicker
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
})
}
}
}
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)
})
}
}
}
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)
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 () }
}
}
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)
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)
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)
letswift(17)
UIBindingObserver, ControlEvent, ControlProperty
UIBindingObserver
• setter 

• error 

• Mainthread binding
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
}
}
}
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
}
}
}
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
}
}
}
ControlEvent
• getter 

• , 

• completed 

• Mainthread
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)
}
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
}
}
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
}
}
ControlProperty
• setter + getter

• a.k.a. UIBindingObserver + Observable

• element shareReplay(1)
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)
}
}
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)
}
}
}
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)
}
}
}
letswift(17)
Disposing
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()
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)
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)
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)
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)
}
letswift(17)
Debugging
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)
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)
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)
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)
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)
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)
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
letswift(17)
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)
.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
subscribe
letswift(17)

RxSwift 활용하기 - Let'Swift 2017

  • 1.
  • 2.
  • 3.
  • 5.
  • 6.
    : 86 :47 subscribe - : 40 : 5
  • 9.
  • 10.
    RxSwift • / • • GCD, KVO, Delegate, NotificationCenter •
  • 11.
    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
  • 12.
    Adding numbers Rx •UITextField text • • • UILabel text https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/Numbers
  • 13.
    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
  • 14.
    Adding numbers .valueChanged funccombineValues() { 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) } }
  • 15.
    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) } }
  • 16.
    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) } }
  • 17.
    Adding numbers override funcviewDidLoad() { 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
  • 18.
    Adding numbers number.rx.text overridefunc 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.
    Adding numbers override funcviewDidLoad() { 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.
    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) }
  • 21.
    func combineValues() { letnum1 = 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) }
  • 24.
    func combineValues() { letnum1 = 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) }
  • 25.
    func combineValues() { letnum1 = 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) }
  • 26.
    func combineValues() { letnum1 = 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) }
  • 27.
    func combineValues() { letnum1 = 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) }
  • 28.
    func combineValues() { letnum1 = 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) }
  • 29.
    func combineValues() { letnum1 = 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) } ?
  • 30.
  • 31.
  • 32.
  • 34.
  • 35.
  • 36.
    Observable enum Event<Element> { casenext(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>) }
  • 37.
    Observable import UIKit import RxSwift importRxCocoa 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
  • 38.
    Observable .combineLatest { Int($0)+ … } “1” “3” “2” 6 number1.rx.text.orEmpty number2.rx.text.orEmpty number3.rx.text.orEmpty
  • 39.
    Observable .combineLatest { Int($0)+ … } 6 .map { $0.description } “6”
  • 40.
    Observable .combineLatest { Int($0)+ … } “1” “3” “2” 6 “6” 10
  • 41.
    Observable .combineLatest { Int($0)+ … } “1” “3” “2” 6 “6” 10 “” 7
  • 42.
  • 43.
  • 44.
    ImagePickerController cameraButton.rx.tap .flatMapLatest { [weakself] _ 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.
    ImagePickerController cameraButton.rx.tap .flatMapLatest { [weakself] _ 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.
    ImagePickerController cameraButton.rx.tap .flatMapLatest { [weakself] _ 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.
    ImagePickerController cameraButton.rx.tap .flatMapLatest { [weakself] _ 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.
    ImagePickerController cameraButton.rx.tap .flatMapLatest { [weakself] _ 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)
  • 49.
    ImagePickerController cameraButton.rx.tap .flatMapLatest { [weakself] _ 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)
  • 50.
    ImagePickerController cameraButton.rx.tap .flatMapLatest { [weakself] _ 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.
    ImagePickerController cameraButton.rx.tap .flatMapLatest { [weakself] _ 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)
  • 52.
    ImagePickerController cameraButton.rx.tap .flatMapLatest { [weakself] _ 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.
    ImagePickerController cameraButton.rx.tap .flatMapLatest { [weakself] _ 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)
  • 54.
    extension Reactive whereBase: 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) }) } } }
  • 55.
    extension Reactive whereBase: 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) }) } } }
  • 56.
    ImagePickerController cameraButton.rx.tap .flatMapLatest { [weakself] _ 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)
  • 57.
    extension Reactive whereBase: 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 () } } }
  • 58.
    cameraButton.rx.tap .flatMapLatest { [weakself] _ 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)
  • 59.
    cameraButton.rx.tap .flatMapLatest { [weakself] _ 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)
  • 60.
    cameraButton.rx.tap .flatMapLatest { [weakself] _ in return UIImagePickerController.rx.createWithParent(self) { picker in picker.sourceType = .camera picker.allowsEditing = false } } .bind(to: imageView.rx.image) .disposed(by: disposeBag)
  • 61.
  • 62.
    UIBindingObserver • setter •error • Mainthread binding
  • 63.
    extension Reactive whereBase: 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 } } }
  • 64.
    public final classUIBindingObserver<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 } } }
  • 65.
    public final classUIBindingObserver<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 } } }
  • 66.
    ControlEvent • getter •, • completed • Mainthread
  • 67.
    extension Reactive whereBase: 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) }
  • 68.
    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 } }
  • 69.
    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 } }
  • 70.
    ControlProperty • setter +getter • a.k.a. UIBindingObserver + Observable • element shareReplay(1)
  • 71.
    extension Reactive whereBase: 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) } }
  • 72.
    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) } } }
  • 73.
    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) } } }
  • 74.
  • 75.
    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()
  • 76.
    Disposing cameraButton.rx.tap .flatMapLatest { [weakself] _ 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)
  • 77.
    Disposing cameraButton.rx.tap .flatMapLatest { [weakself] _ 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)
  • 78.
    Disposing cameraButton.rx.tap .flatMapLatest { [weakself] _ 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)
  • 79.
    Disposing extension Reactive whereBase: 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) }
  • 80.
  • 81.
    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)
  • 82.
    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)
  • 83.
    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)
  • 84.
    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)
  • 85.
    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)
  • 86.
    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)
  • 87.
    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
  • 88.
  • 89.
    flatMap vs. withLatestFrom letbuttonTap = 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)
  • 90.
    .just() and .share() letobservable = 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
  • 91.
  • 93.