How to Convert Delegate to Observable Rxswift

How to convert Delegate to Observable RxSwift?

Due to the popularity of this answer, I have written an article about it: Convert a Swift Delegate to RxSwift Observables

I believe this is the official way of converting a delegate into RxObservables:

class CardInfoTextField: NSObject {
weak var delegate: CardInfoTextFieldDelegate? = nil
}

@objc
protocol CardInfoTextFieldDelegate {
@objc optional func textField(_ textField: CardInfoTextField, didEnterValidInfo: String)
@objc optional func textField(_ textField: CardInfoTextField, didEnterPartiallyValidInfo: String)
@objc optional func textField(_ textField: CardInfoTextField, didEnterOverflowInfo overFlowDigits: String)
}

extension CardInfoTextField: HasDelegate {
public typealias Delegate = CardInfoTextFieldDelegate
}

class CardInfoTextFieldDelegateProxy
: DelegateProxy<CardInfoTextField, CardInfoTextFieldDelegate>
, DelegateProxyType
, CardInfoTextFieldDelegate {

//#MARK: DelegateProxy
init(parentObject: CardInfoTextField) {
super.init(parentObject: parentObject, delegateProxy: CardInfoTextFieldDelegateProxy.self)
}

public static func registerKnownImplementations() {
self.register { CardInfoTextFieldDelegateProxy(parentObject: $0) }
}
}

extension Reactive where Base: CardInfoTextField {
var delegate: CardInfoTextFieldDelegateProxy {
return CardInfoTextFieldDelegateProxy.proxy(for: base)
}

var didEnterValidInfo: Observable<String> {
return delegate.methodInvoked(#selector(CardInfoTextFieldDelegate.textField(_:didEnterValidInfo:)))
.map { $0[1] as! String }
}

var didEnterPartiallyValidInfo: Observable<String> {
return delegate.methodInvoked(#selector(CardInfoTextFieldDelegate.textField(_:didEnterPartiallyValidInfo:)))
.map { $0[1] as! String }
}

var didEnterOverflowInfo: Observable<String> {
return delegate.methodInvoked(#selector(CardInfoTextFieldDelegate.textField(_:didEnterOverflowInfo:)))
.map { $0[1] as! String }
}
}

Once you have the above, you should be able to:

let validInfo: Observable<String> = myCardInfoTextField.rx.didEnterValidInfo

How to properly convert a 3rd party library delegate into a RxSwift Observable

So after digging some more, it looks like this will do the trick with a required delegate method, updated for RxSwift 3.3.1. This is using their DelegateProxy system.

import RxSwift
import RxCocoa
import Lib


public final class RxLibDelegate: DelegateProxy, LibDelegate, DelegateProxyType{

let _subject = PublishSubject<[LibResult]>()

public static func currentDelegateFor(_ object: AnyObject) -> AnyObject?{
let target = object as! Lib
return target.delegate
}

public static func setCurrentDelegate(_ delegate: AnyObject?, toObject object: AnyObject) {
let target = object as! Lib
target.delegate = delegate as? LibDelegate
}

public func lib(_ lib: Lib, didFinishWithResult results: [LibResult]) {
_subject.onNext(results)
_subject.onCompleted()
}
}



extension Lib{

public var rx_delegate: DelegateProxy{
// `proxyForDelegate` moved as compared to examples at:
// https://samritchie.net/2016/05/12/rxswift-delegateproxy-with-required-methods/
// https://medium.com/@maxofeden/rxswift-migrate-delegates-to-beautiful-observables-3e606a863048#.rksg2ckpj

return RxLibDelegate.proxyForObject(self)
}

public var rx_libResults: Observable<[LibResult]> {
// `proxyForDelegate` moved as compared to examples at:
// https://samritchie.net/2016/05/12/rxswift-delegateproxy-with-required-methods/
// https://medium.com/@maxofeden/rxswift-migrate-delegates-to-beautiful-observables-3e606a863048#.rksg2ckpj

let proxy = RxLibDelegate.proxyForObject(self)
return proxy._subject
}
}

That's about 28 LOC. My original "wrapper" (see updated version below) but I don't know if it's the best is 21 LOC; 6 of 1 half dozen of the other?

In my particular case I only have 1 delegate method to worry about. If you were working with some functionality that had multiple delegates I think the DelegateProxy + extension methods would be a lot more practical and the better choice in that case.

Regarding my original trial wrapping thing using that Void observable, it appears it's totally acceptable to alter the stream with flatMapLatest as evidenced here re: Sending continual events while a button is pressed:

https://stackoverflow.com/a/39123102/1060314

import RxSwift
import RxCocoa


let button = submitButton.rx_controlEvent([.TouchDown])
button
.flatMapLatest { _ in
Observable<Int64>.interval(0.1, scheduler: MainScheduler.instance)
.takeUntil(self.submitButton.rx_controlEvent([.TouchUpInside]))
}
.subscribeNext{ x in print("BOOM \(x)") }
.addDisposableTo(disposeBag)

//prints BOOM 0 BOOM 1 BOOM 2 BOOM 3 BOOM 4 BOOM 5 for every 0.1 seconds

Note that a new Observable is returned from flatMapLatest. The author cites the RxSwift slack channel, so I assume it is at least acceptable to do.

Here's an updated version of my wrapper version that I think might be a bit cleaner:

import RxSwift


public final class RxLibBridge: LibDelegate{

let lib = Lib()
let _source = PublishSubject<[LibResult]>()

public init(){
lib.delegate = self
}

public func asObservable() -> Observable<[LibResult]>{
// create a cold observable to start
// the Lib's async operation on subscribe.
return Observable.just(())
.do(onNext: {
self.lib.startOperation()
})
.flatMapLatest{self._source}
}

// the lib's completion delegate method
public func lib(_ lib: Lib, didFinishWithResult results: [LibResult]) {
// grab the PublishSubject, emit the result and complete
_source.onNext(results)
_source.onCompleted()
}
}

How to pass data from delegate method to the observable's onNext method in RxSwift?

I've made a lot of assumptions in the below, but the result should look something like this:

class WeightMachineManager {

var connectedDevice: IWDevice?

func setup() {
IWDeviceManager.shared()?.initMgr()
}

func listenToWeight() -> Observable<IWWeightData> {
if let connectedDevice = connectedDevice, let deviceManager = IWDeviceManager.shared() {
return deviceManager.rx.add(connectedDevice)
.flatMap { deviceManager.rx.receivedWeightData() } // maybe this should be flatMapLatest or flatMapFirst. It depends on what is calling listenToWeight() and when.
}
else {
return .error(NSError.init(domain: "WeightMachineManager", code: -1, userInfo: nil))
}
}
}

extension IWDeviceManager: HasDelegate {
public typealias Delegate = IWDeviceManagerDelegate
}

class IWDeviceManagerDelegateProxy
: DelegateProxy<IWDeviceManager, IWDeviceManagerDelegate>
, DelegateProxyType
, IWDeviceManagerDelegate {

init(parentObject: IWDeviceManager) {
super.init(parentObject: parentObject, delegateProxy: IWDeviceManagerDelegateProxy.self)
}

public static func registerKnownImplementations() {
self.register { IWDeviceManagerDelegateProxy(parentObject: $0) }
}
}

extension Reactive where Base: IWDeviceManager {

var delegate: IWDeviceManagerDelegateProxy {
return IWDeviceManagerDelegateProxy.proxy(for: base)
}

func add(_ device: IWDevice) -> Observable<Void> {
return Observable.create { observer in
self.base.add(device, callback: { device, code in
if code == .success {
observer.onNext(())
observer.onCompleted()
}
else {
observer.onError(NSError.init(domain: "IWDeviceManager", code: -1, userInfo: nil))
}
})
return Disposables.create()
}
}

func receivedWeightData() -> Observable<IWWeightData> {
return delegate.methodInvoked(#selector(IWDeviceManagerDelegate.onReceiveWeightData(_:data:)))
.map { $0[1] as! IWWeightData }
}
}

How to replace a simple delegate protocol with RxSwift?

You trying to pass information about selected items (Correct me if I wrong).

Currently selected items

We don't care about selection / unselection, we care only about items, that selected in each moment of time.

This is probably most common use case, since your user (user of component) doesn't need to have local state.

protocol TopicSelectionProvider {
var selectedTopicsStream: Observable<Set<RTopic>> { get }
}

class MyTableDelegate: NSObject, UITableViewDelegate {
private var selectedTopics = BehaviourRelay<Set<RTopic>>([])
...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var selected = selectedTopics.value
selected.insert(topics[indexPath.row])
selectedTopics.accept(selected)
}

func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
var selected = selectedTopics.value
selected.remove(topics[indexPath.row])
selectedTopics.accept(selected)
}
}

extension MyTableDelegate: TopicSelectionProvider {
var selectedTopicsStream: Observable<Set<RTopic>> {
return selectedTopics.asObservable()
}
}

Selection / Unselection events

We don't care about selected items, we only interested in events them self.

This use case can be used, when you don't need items, but events (for api calls for example).

enum TopicSelectionEvent {
case select(RTopic)
case deselect(RTopic)
}

protocol TopicSelectionProvider {
var topicSelectionStream: Observable<TopicSelectionEvent> { get }
}

class MyTableDelegate: NSObject, UITableViewDelegate {
private var topicSelection = PublishSubject<TopicSelectionEvent>()
...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
topicSelection.onNext(.select(topics[indexPath.row]))
}

func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
topicSelection.onNext(.deselect(topics[indexPath.row]))
}
}

extension MyTableDelegate: TopicSelectionProvider {
var topicSelectionStream: Observable<TopicSelectionEvent> {
return topicSelection.asObservable()
}
}

Both approach valid and have proc and cons. Choose one that suits you best.

TabelView Delegate methods using RxSwift

Conform your View Controller

class YourViewController: UIViewController, UITableViewDelegate

Then add this in viewDidLoad()

tableView.rx.setDelegate(self).addDisposableTo(disposeBag)

then you write UITableViewDelegate methods.

Convert [Observable T ] to Observable [T] in RxSwift

You can use combine to convert from [Observable<CityMappable>] to Observable<[CityMappable]>.

Try this code

let observableCities = favCitiesID.flatMap { cityIds -> Observable<[CityMappable]> in
let obs = cityIds.map{ return self.apiManager.getCurrentWeatherData(for: $0)}
return Observable.combineLatest(obs)
}

How to use dataSource and delegate methods with RxSwift

The basic RxCocoa pod which you should have imported with RxSwift in the podfile should already had this.

In case you didn't import that in your podfile, add

pod 'RxCocoa',    '~> 3.0'

Proceed then to bind the delegate like you would with a normal TableView. I didn't see any dataSource observable for UIPickerView though, so you should check that.



Related Topics



Leave a reply



Submit