如何在 属性 上过滤 Observable?
How do I filter a Observable on a property that is an Observable?
问题是我有一个协议,其中包含我想要在可用时对其进行排序的属性。因为我正在努力让一切变得超级反应。
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 returns 一个 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
}
}
}
}
诊断
根据我对您问题的理解,您在执行过滤操作时遇到困难,因为该属性是 Observable<Int>
而不是 Int
。这意味着您不能仅使用相等运算符检查 ID,因为您 need to get out of the Rx monad.
继续使用 Rx Monad
更优雅的 FRP 解决方案:
- 定义您的设备可观察对象。
- 定义可观察的设备 ID。
- Zip他们在一起。
- Filter by checking 最后一个设备 ID。
- Return 到您想要的数据类型 (
DeviceConnectionProtocol
)。
- 使用最后一个设备执行必要的代码。
这是 RxSwift 代码。我的一些 class 定义可以在下面的代码块中看到。
// 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) { [=10=] }
// data type of (DeviceConnectionProtocol, Int)
.filter { [=10=].1 == lastID }
.map { [=10=].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
运算符,但由于某些原因,您的情况不同并且它不起作用。
参考和注释
如果你想要在 Rx Playground 上运行的完整代码,这里有 Github Gist。
在 RxMarbles 上可视化的 zip
运算符。
zip
运算符documentation.
filter
运算符在 RxMarbles 上可视化。
filter
运算符documentation.
这是一个Swift 3的解决方案。
问题是我有一个协议,其中包含我想要在可用时对其进行排序的属性。因为我正在努力让一切变得超级反应。
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 returns 一个 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
}
}
}
}
诊断
根据我对您问题的理解,您在执行过滤操作时遇到困难,因为该属性是 Observable<Int>
而不是 Int
。这意味着您不能仅使用相等运算符检查 ID,因为您 need to get out of the Rx monad.
继续使用 Rx Monad
更优雅的 FRP 解决方案:
- 定义您的设备可观察对象。
- 定义可观察的设备 ID。
- Zip他们在一起。
- Filter by checking 最后一个设备 ID。
- Return 到您想要的数据类型 (
DeviceConnectionProtocol
)。 - 使用最后一个设备执行必要的代码。
这是 RxSwift 代码。我的一些 class 定义可以在下面的代码块中看到。
// 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) { [=10=] }
// data type of (DeviceConnectionProtocol, Int)
.filter { [=10=].1 == lastID }
.map { [=10=].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
运算符,但由于某些原因,您的情况不同并且它不起作用。
参考和注释
如果你想要在 Rx Playground 上运行的完整代码,这里有 Github Gist。
在 RxMarbles 上可视化的
zip
运算符。zip
运算符documentation.filter
运算符在 RxMarbles 上可视化。filter
运算符documentation.这是一个Swift 3的解决方案。