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

Combine this!

Avatar for miserya miserya
September 04, 2019

Combine this!

The presentation is an overview of Combine framework for iOS development.

Avatar for miserya

miserya

September 04, 2019
Tweet

More Decks by miserya

Other Decks in Programming

Transcript

  1. Imperative programming UITextField we need to track text changing set

    textField delegate add realization of textField(_:shouldChangeCharactersIn:replacementString:) method in delegate wait until delegate method will be called
  2. Reactive programming UITextField we need to track text changing UITextField

    H He Hel Hell Hello User Input produce a flow of User Input
  3. UITextField we need to track text changing subscribe to UITextFiled

    text change event Text Changed handle new Text Input Reactive programming
  4. Publisher Subscriber deliver a sequence of values over time subscribes

    to the publisher to receive its elements Observer Observable
  5. Publisher Subscriber _ = Publishers.Just("Hello, world!").sink { (value: String) in

    print(value) } _ = Publishers.Sequence<[Int], Never>(sequence: [1, 3, 6]) .sink { (value: Int) in print(value) }
  6. Publisher Subscriber _ = Publishers.Just("Hello, world!").sink { (value: String) in

    print(value) } _ = Publishers.Sequence<[Int], Never>(sequence: [1, 3, 6]) .sink { (value: Int) in print(value) } OUTPUT: Hello, world! 1 3 6
  7. Publisher Subscriber _ = Observable<String>.just("Hello, world!") .subscribe { (value: Event<String>)

    in print(value.element ?? "") } _ = Observable<Int>.of(1, 2, 3) .map({ return String($0) }) .subscribe { (value: Event<String>) in print(value.element ?? "") } OUTPUT: Hello, world! 1 2 3
  8. Publisher Subscriber _ = Publishers.Just("Hello, world!") _ = Publishers.Sequence<[Int], Never>(sequence:

    [1, 3, 6]) class TextPrinter { let name: String init(name: String) { self.name = name } var text: String = "" { didSet { print(name+": "+text) } } }
  9. Publisher Subscriber let printer1 = TextPrinter(name: "printer1") let printer2 =

    TextPrinter(name: "printer2") _ = Publishers.Just("Hello, world!").assign(to: \.text, on: printer1) _ = Publishers.Sequence<[Int], Never>(sequence: [1, 3, 6]) .map({ String($0) }) .assign(to: \TextPrinter.text, on: printer2) class TextPrinter { let name: String init(name: String) { self.name = name } var text: String = "" { didSet { print(name+": "+text) } } }
  10. Publisher Subscriber let printer1 = TextPrinter(name: "printer1") let printer2 =

    TextPrinter(name: "printer2") _ = Publishers.Just("Hello, world!").assign(to: \.text, on: printer1) _ = Publishers.Sequence<[Int], Never>(sequence: [1, 3, 6]) .map({ String($0) }) .assign(to: \TextPrinter.text, on: printer2) class TextPrinter { let name: String init(name: String) { self.name = name } var text: String = "" { didSet { print(name+": "+text) } } } OUTPUT: printer1: Hello, world! printer2: 1 printer2: 3 printer2: 6
  11. Publisher Subscriber let printer1 = TextPrinter(name: “printer1") _ = Observable<Int>.of(1,

    2, 3) .map({ return String($0) }) .bind(to: printer1.rx.text) extension TextPrinter: ReactiveCompatible { } extension Reactive where Base: TextPrinter { internal var text: Binder<String> { return Binder(self.base) { printer, text in printer.text = text } } }
  12. Publisher Subscriber let printer1 = TextPrinter(name: "printer1") let printer2 =

    TextPrinter(name: "printer2") let publisher1 = Publishers.Just("Hello, world!") let publisher2 = Publishers.Sequence<[Int], Never>(sequence: [1, 3, 6]) .map({ String($0) }) let subscriber1 = Subscribers.Assign(object: printer1, keyPath: \.text) let subscriber2 = Subscribers.Assign(object: printer2, keyPath: \.text) publisher1.subscribe(subscriber1) publisher2.subscribe(subscriber2) class TextPrinter { let name: String init(name: String) { self.name = name } var text: String = "" { didSet { print(name+": "+text) } } }
  13. let printer1 = TextPrinter(name: "printer1") let printer2 = TextPrinter(name: "printer2")

    let publisher1 = Publishers.Just("Hello, world!") let publisher2 = Publishers.Sequence<[Int], Never>(sequence: [1, 3, 6]) .map({ String($0) }) let subscriber1 = Subscribers.Assign(object: printer1, keyPath: \.text) let subscriber2 = Subscribers.Assign(object: printer2, keyPath: \.text) publisher1.subscribe(subscriber1) publisher2.subscribe(subscriber2) Publisher Subscriber class TextPrinter { let name: String init(name: String) { self.name = name } var text: String = "" { didSet { print(name+": "+text) } } } OUTPUT: printer1: Hello, world! printer2: 1 printer2: 3 printer2: 6
  14. class LoginViewController: UIViewController, UITextFieldDelegate { @IBOutlet weak var btnSignIn: UIButton!

    private var btnSignInEnabledPublisher: AnyCancellable? private lazy var viewModel: LoginViewModel = { return LoginViewModel() }() override func viewDidLoad() { super.viewDidLoad() btnSignInEnabledPublisher = viewModel.$loginState .map({ return $0 == .signInEnabled }) .assign(to: \.isEnabled, on: btnSignIn) } ... } @Published class LoginViewModel { @Published private(set) var loginState: LoginState = .signInDisabled { didSet { switch loginState { case .signInDisabled: break case .signInEnabled: break case .signInStarted: performSignInRequest() } } } ... }
  15. is a generic data structure that encapsulates read/write access to

    a property while adding some extra behaviour to “augment” its semantics. Property wrapper
  16. Property wrapper example @propertyWrapper struct UUIDIdetified<Value> { let id =

    UUID() var wrappedValue: Value init(wrappedValue: Value) { self.wrappedValue = wrappedValue } } @UUIDIdetified var text = "Some Text” print($text.id) OUTPUT: 70665FD5-AFA5-466F-96BB-3DD9611D0D32
  17. Property wrapper example @propertyWrapper struct UserDefault<T> { let key: String

    let defaultValue: T init(_ key: String, defaultValue: T) { self.key = key self.defaultValue = defaultValue } var wrappedValue: T { get { return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } set { UserDefaults.standard.set(newValue, forKey: key) } } } @UserDefault("has_seen_app_introduction", defaultValue: false) var isHasSeenAppIntro: Bool
  18. If you want to cancel a subscription you can do

    that by calling dispose on it. You can also add the subscription to a DisposeBag which will cancel the subscription for you automatically on deinit of the DisposeBag Instance. RxSwift Memory Management
  19. No something like DisposeBag in Combine private var emailPublisher: AnyCancellable?

    private var emailEditablePublisher: AnyCancellable? private var passwordPublisher: AnyCancellable? private var passwordEditablePublisher: AnyCancellable? private var btnSignInEnabledPublisher: AnyCancellable?
  20. Subjects in Combine are basically publishers on which we can

    subscribe but also dynamically send events to them.
  21. Subjects in Combine are basically publishers on which we can

    subscribe but also dynamically send events to them. PassthroughSubject CurrentValueSubject
  22. PassthroughSubject example let subject = PassthroughSubject<String, Never>() let publisher =

    subject.eraseToAnyPublisher() let subscriber1 = publisher.sink(receiveValue: { value in print(value) }) //subscriber1 will recive the events but not the subscriber2 subject.send("Event1") subject.send("Event2") let subscriber2 = publisher.sink(receiveValue: { value in print(value) }) //Subscriber1 and Subscriber2 will recive this event subject.send("Event3") OUTPUT: Event1 Event2 Event3 Event3
  23. Schedulers let selectedFilter = PassthroughSubject<String, Never>() let searchText = NotificationCenter.default

    .publisher(for: UITextField.textDidChangeNotification, object: textFieldEmail) .map( { ($0.object as! UITextField).text } ) .debounce(for: .milliseconds(500), scheduler: RunLoop.main) .eraseToAnyPublisher() let publisher = Publishers.CombineLatest(selectedFilter, searchText) .map({ (selectedFilter, searchText) in "\(selectedFilter) \(searchText)" }) _ = publisher.sink { value in print(value) }
  24. RxSwift Schedulers let publish1 = PublishSubject<Int>() let publish2 = PublishSubject<Int>()

    let concurrentScheduler = ConcurrentDispatchQueueScheduler(qos: .background) Observable.of(publish1, publish2) .observeOn(concurrentScheduler) .merge() .subscribeOn(MainScheduler()) .subscribe(onNext:{ print($0) }) publish1.onNext(20) publish1.onNext(40) OUTPUT: 20 40
  25. Filtering: filter _ = Publishers.Sequence(sequence: [1,2,2,3,3,4,7]) .map { $0 *

    2 } .filter { $0.isMultiple(of: 2) } .dropFirst(3) .removeDuplicates() .sink(receiveValue: { value in print(value) })
  26. Combining: merge let germanCities = PassthroughSubject<String, Never>() let italianCities =

    PassthroughSubject<String, Never>() let europianCities = Publishers.Merge(germanCities, italianCities) _ = europianCities.sink(receiveValue: { city in print("\(city) is a city in europe") })
  27. Combining: combine Latest let selectedFilter = PassthroughSubject<String, Never>() let searchText

    = PassthroughSubject<String, Never>() let publisher = Publishers.CombineLatest(selectedFilter, searchText) .sink { (selectedFilter, searchText) in "\(selectedFilter) \(searchText)" }
  28. Conclusions Combine is available since iOS13 Combine is faster than

    RxSwift RxSwift has more operators and other abilities… yet RxSwift has big community MVVM is coming!