关于rx swift:如何使用BehaviorRelay替代RxSwift中的Variable?

How to use BehaviorRelay as an alternate to Variable in RxSwift?

从RxSwift4开始,Variable移至Deprecated.swift,表示将来可能弃用Variable。对Variable提出的另一种选择是BehaviorRelay。发布此问题时,由于我在BehaviorRelay上找不到很多网上教程,因此在SO中发布了这样一个基本问题。

假设我正在进行一个webService调用,并且接收到一大堆数据,即JSONArray,则一一解析JSON对象,然后更新了Variable的value属性

这是我的变量声明

1
var myFilter = Variable<[MyFilterModel]>([MyFilterModel(data:"{:}")])

每次获取新元素时,我将我的变量更新为

1
myFilter.value.append(newModel)

当Variable绑定到CollectionView时,collectionVie将立即使用新添加的对象更新其UI。

BehaviorRelay问题

现在我的声明看起来像

1
var myFilter = BehaviorRelay<[MyFilterModel]>(value: [MyFilterModel(data:"{:}")])

但是最大的问题是myFilter.value是readOnly。所以很明显

1
myFilter.value.append(newModel)

不是解决方案。我发现我可以使用accept

但是现在当我尝试解析每个元素作为响应并更新myFilter的值时

1
self?.expertsFilter.accept(newModel)

上面的语句给出了错误引用

Can not convert the value of NewModel to expected arguement type
[NewModel]

显然,它期望数组而不是单个元素。

解决方法:

解决方案1:

因此,一种解决方案是将所有响应累积在一个临时数组中,一旦完成触发self?.expertsFilter.accept(temporary_array)

解决方案2:

如果在解析每个元素时必须向订阅者发送onNext事件,则需要将self?.expertsFilter的值复制到新Array,将新解析的元素添加到该元素并返回新数组。

解决方案3:

摆脱BehaviorRelay并使用BehaviorSubject / PublishSubject

前两个声音令人沮丧,因为解析每个元素时可能需要触发UI,我迫不及待要解析整个响应。因此很明显,solution1并没有太多用处。

第二种解决方案更可怕,因为它每次发送onNext事件时都会创建一个新数组(我知道它是临时的,将被释放)。

题:

因为在困境中提出了BehaviorRelay替代Variable的建议,所以正确使用accept了吗?有更好的解决方法吗?

请帮忙


您是否考虑过简单地从中继上的现有值创建一个新数组,然后追加并调用accept

1
myFilter.accept(myFilter.value + [newModel])


我写了这个扩展,用BehaviorRelay代替了Variable。 您可以根据此模式添加所需的任何方法来轻松迁移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public extension BehaviorRelay where Element: RangeReplaceableCollection {

    public func insert(_ subElement: Element.Element, at index: Element.Index) {
        var newValue = value
        newValue.insert(subElement, at: index)
        accept(newValue)
    }

    public func insert(contentsOf newSubelements: Element, at index: Element.Index) {
        var newValue = value
        newValue.insert(contentsOf: newSubelements, at: index)
        accept(newValue)
    }

    public func remove(at index: Element.Index) {
        var newValue = value
        newValue.remove(at: index)
        accept(newValue)
    }
}

现在,您写的是BehaviorRelay.funcName,而不是Variable.value.funcName

使用where Element: RangeReplaceableCollection子句的想法来自retendo的答案

另请注意,index的类型为Element.Index,而不是Int或其他类型。


以道尔顿的答案为基础,这是一个方便的扩展:

1
2
3
4
5
extension BehaviorRelay where Element: RangeReplaceableCollection {
    func acceptAppending(_ element: Element.Element) {
        accept(value + [element])
    }
}


我会做这样的事情-

1
2
3
4
5
6
7
8
9
10
11
let requests = PublishSubject<Observable<ServerResponse>>.create()
let responses: Observable<ServerResponse> = requests.switchLatest()

let parsed: Observable<[ParsedItem]> = responses
  .flatMap { Observable.from($0).map { parse($0) }.toArray() }

parsed.bind(to: ui)

// repeated part
let request1: Observable<ServerResponse> = servive.call()
request.onNext(request1)


我创建了这些扩展,使用了两种方法来方便迁移,以防万一您拥有数组变量并且必须使用append。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    extension BehaviorRelay where Element: RangeReplaceableCollection {

        func append(_ subElement: Element.Element) {
            var newValue = value
            newValue.append(subElement)
            accept(newValue)
        }

        func append(contentsOf: [Element.Element]) {
            var newValue = value
            newValue.append(contentsOf: contentsOf)
            accept(newValue)
        }

        public func remove(at index: Element.Index) {
            var newValue = value
            newValue.remove(at: index)
            accept(newValue)
        }

        public func removeAll() {
            var newValue = value
            newValue.removeAll()
            accept(newValue)
        }

    }

你这样称呼它

1
2
3
4
5
6
    var things = BehaviorRelay<[String]>(value: [])
    things.append("aa")
    let otherThings = ["bb","cc"]
    things.append(contentsOf: otherThings)
    things.remove(at: 0)
    things.removeAll()

AshKan的答案很好,但我来这里是为了寻找解决方案中缺少的方法。
附加:

1
2
3
4
5
6
7
8
9
extension BehaviorRelay where Element: RangeReplaceableCollection {

    func append(_ subElement: Element.Element) {
        var newValue = value
        newValue.append(subElement)
        accept(newValue)
    }

}