如何正确地将第三方库委托转换为RxSwift Observable

时间:2017-03-23 13:38:45

标签: swift rx-swift

我有一个案例,我正在使用第三方库,我想把它变成一个Observable。适当地,库是围绕代表设计的,正如人们所期望的那样,我正在包装它。该库执行异步操作,并在完成时将结果调用它的委托。

我绝对想利用observable的cold性质,只有在有人订阅时才开始操作。我有一个有效的解决方案,我只是不知道它是否存在严重缺陷,我错过了对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<Void>.create{
            observer in

            self.lib.startOperation()

            // emit and complete
            observer.onNext(())
            observer.onCompleted()
            return Disposables.create()
        }
        // convert the `Void` observable into an observable from the 
        // PublishSubject
        .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
        let observer = _source.asObserver()
        observer.onNext(results)
        observer.onCompleted()
    }
}

所以我的问题是:这是Rx中的合适模式吗?它再次起作用:

RxLibBridge()
    .asObservable()
    .subscribe(...)

仅仅因为它起作用并不意味着我没有从根本上误解了适应这种情况的正确方法。

我知道RxSwift中有一种方法可以处理这样的事情:

https://medium.com/@maxofeden/rxswift-migrate-delegates-to-beautiful-observables-3e606a863048#.rksg2ckpj

https://samritchie.net/2016/05/12/rxswift-delegateproxy-with-required-methods/

我尝试了这种方法,但看起来自2015年以来API发生了变化。也就是说,在示例中,在扩展程序中添加proxyForObject方法时,无法找到rx_delegate上方的链接。

此外,这种方法似乎更倾向于纯Objective-C [UIKit / AppKit] API。在我尝试关注链接的示例时,我正在编辑第三方lib的源代码以生成委托方法optional并将其公开给@objc。 lib的委托是required,我宁愿不用分叉来进行修改。

此SO答案提供了上述2个链接的更新API:

Can not use proxyForObject function in DelegateProxyType (rxSwift)

1 个答案:

答案 0 :(得分:3)

因此,在挖掘了一些之后,看起来这将使用所需的委托方法,为RxSwift 3.3.1更新。这是使用他们的DelegateProxy系统。

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
    }
}

那是大约28 LOC。我原来的“包装”(见下面的更新版本),但我不知道它是最好的是21 LOC;另外6个中的6个?

在我的特定情况下,我只需要担心1个委托方法。如果您正在使用具有多个委托的某些功能,我认为DelegateProxy + extension方法将更加实用,并且在这种情况下是更好的选择。

关于使用Void可观察对象的原始试用包装内容,使用flatMapLatest更改流是完全可以接受的,如此处所示:在按下按钮时发送连续事件:

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

请注意,Observable会返回新的flatMapLatest。作者引用了RxSwift slack channel,所以我认为它至少是可以接受的。

这是我的包装版本的更新版本,我认为可能更清洁一点:

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()
    }
}