如何在Observable属性上过滤Observable?

时间:2016-11-03 15:45:00

标签: ios swift3 rx-swift

问题是我有一个包含我想要在可用时排序的属性的协议。因为我正在努力使一切都具有超反应性。

protocol DeviceConnectionProtocol {
...
    var id : Observable<String> { get }
...
}

我正处于这样一种情况,即我想找到我连接的最后一台设备而不管url / name / etc如何改变。

  class DeviceFinder {

    let rx_DeviceList = Variable([DeviceConnectionProtocol]())
    let disposeBag = DisposeBag()

    init() {
        SMOIPConnection.FindDevices().subscribe(onNext : { smoip in
            self.rx_DeviceList.value.append(smoip)
        }).addDisposableTo(disposeBag)

        MockDevice.FindDevices().subscribe(onNext : { mock in
            self.rx_DeviceList.value.append(mock)
        }).addDisposableTo(disposeBag)

    }

}

...

这是我到目前为止排序的功能。但它不可行,因为device.id.map返回一个Observable而不是过滤操作所需的Bool

struct LastConnectedDevice {

    private static let lastConnectedKeyForID = "lastConnected"

    static func get() -> Observable<DeviceConnectionProtocol>{
    let lastID = UserDefaults.standard.string(forKey: lastConnectedKeyForID)
       return DeviceFinder().rx_DeviceList.asObservable().flatMap{list in
            return Observable.from(list)
            }.filter { (device : DeviceConnectionProtocol) -> Bool in
                return device.id.map{ id in
                    return id == lastID
                }
        }
    }
}

1 个答案:

答案 0 :(得分:3)

诊断

根据我对您的问题的理解,您在执行过滤操作时遇到了困难,因为该属性是Observable<Int>而不是Int。这意味着您不能仅使用相同的运算符检查ID,因为您need to get out of the Rx monad

留在Rx Monad

更优雅的FRP解决方案:

  1. 定义您的设备可观察。
  2. 定义您的设备ID可观察。
  3. Zip他们在一起。
  4. Filter checking最后一个设备ID。
  5. 返回所需的数据类型(DeviceConnectionProtocol)。
  6. 使用最后一个设备执行必要的代码。
  7. 这是RxSwift代码。我的一些类定义可以在这个下面的代码块中看到。

    // Observable<DeviceConnectionProtocol>
    let devices = rx_DeviceList
        .asObservable()
        .flatMap { array in Observable.from(array) } 
    
    // Observable<Int>
    let deviceIDs = devices
        .flatMap { device in device.id } 
    
    Observable.zip(devices, deviceIDs) { $0 } 
    
        // data type of (DeviceConnectionProtocol, Int)
        .filter { $0.1 == lastID }
        .map { $0.0 }
        .subscribe(onNext: lastDeviceSelected)
    

    Monad只是一种说范式,处理方式或思维方式的方式。您始终可以退出RxMonad,这意味着保留数据流并返回命令式代码。这是程序员更习惯的。

    退出Rx Monad的简单方法

    这是一个(有点凌乱)的解决方案。 TL; DR转到如何同步退出RxMonad 部分。

    import RxSwift
    
    protocol DeviceConnectionProtocol {
        var id : Observable<String> { get }
    }
    
    
    class Person : DeviceConnectionProtocol {
        var myName: String! = nil
    
        init(name: String) {
            self.myName = name
        }
    
        var id: Observable<String> {
            return Observable.create { [unowned self] obx in
                obx.onNext(self.myName)
                return Disposables.create()
            }
        }
    }
    let rx_DeviceList = Variable([DeviceConnectionProtocol]())
    let disposeBag = DisposeBag()
    
    let lastID = "Hillary"
    
    func lastDeviceSelected(device: DeviceConnectionProtocol) {
        if let person = device as? Person {
            print(person.myName + " was found!")
        }
    }
    
    rx_DeviceList
        .asObservable()
        .flatMap { array in Observable.from(array) }
        .filter{ (device: DeviceConnectionProtocol) -> Bool in
    
            // How to exit the RxMonad synchronously
    
            // Have a result variable
            var currentID = ""
    
            // Subscribe on the observable on the default schedulers (main thread) and assign result
            device.id.subscribe(onNext: { (id: String) in currentID = id })
    
            // Return result
            return lastID == currentID
        }
        .subscribe(onNext: lastDeviceSelected)
    
    rx_DeviceList.value = [Person(name: "Donald"), Person(name: "Goofy"), Person(name: "Hillary")]
    

    结果:

    Hillary was found!
    

    请注意,结束的序列(例如[1,2,3,4,5])可以使用takeLast运算符但由于某种原因,您的情况不同而且无效。

    参考和注释

    1. 如果你想要在Rx Playground上运行的整个代码,这里是Github Gist

    2. RxMarbles上可视化zip运算符。

    3. zip运算符documentation

    4. RxMarbles上可视化filter运算符。

    5. filter运算符documentation

    6. 这是一个Swift 3解决方案。