【问题标题】:How to use BehaviorRelay as an alternate to Variable in RxSwift?如何在 RxSwift 中使用 BehaviorRelay 作为变量的替代品?
【发布时间】:2018-05-07 05:56:16
【问题描述】:

从 RxSwift4 开始,Variable 移至 Deprecated.swift,这标志着将来可能会弃用 Variable。向Variable 提议的替代方案是BehaviorRelay。在发布这个问题时,由于我使用BehaviorRelay 在网络上找不到太多教程,所以我在 SO 中发布了这样一个基本问题。

假设我有一个 webService 调用正在进行,并且我收到一大块 JSONArray 数据,在逐个解析 JSON 对象时,我会更新变量的 value 属性

这是我的变量声明

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

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

myFilter.value.append(newModel)

由于 Variable 绑定到 CollectionView,collectionVie 会立即使用新添加的对象更新其 UI。

行为中继问题

现在我的声明看起来像

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

但最大的问题是myFilter.value只读。很明显

myFilter.value.append(newModel) 

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

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

self?.expertsFilter.accept(newModel)

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

无法将 NewModel 的值转换为预期的争论类型 [新模型]

显然,它需要一个数组而不是单个元素。

解决方法:

解决方案 1:

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

解决方案 2:

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

解决方案 3:

摆脱BehaviorRelay并使用BehaviorSubject/PublishSubject

前两个听起来令人沮丧,因为在解析每个元素时可能需要触发 UI,我不能等到整个响应被解析。所以很明显solution1用处不大。

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

问题:

因为BehaviorRelay 被提议作为Variable 的替代品,我处于两难境地,我是否正确使用accept?有没有更好的方法来解决它?

请帮忙

【问题讨论】:

  • @maxim-volgin : 请看一下
  • 你有什么新的数组解决方案吗?
  • 我无法访问 BehaviorRelay。任何想法为什么?我的 pod 文件只指向 pod 'RxSwift', '~> 4.0'
  • @shabarinath-pabba:确保你在使用BehaviorRelay的文件中使用import RxSwift import RxCocoa导入RxSwift和RxCocoa。 BehaviorRelay 在 RxSwift 中声明,所以逻辑上导入 import RxSwift 就足够了,但如果你将它与 Cocoa 一起使用,import RxCocoa 将是必要的
  • @ShabarinathPabba 导入 RxCocoa。 BehaviorRelay 在 RxCocoa 中声明。

标签: rx-swift


【解决方案1】:

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

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

【讨论】:

  • 这就是我最终做的:) 但想知道它应该如何使用?这是正确的方法吗?但是到现在为止我已经使用了很多次了,现在我觉得这是正确的方法:) 因此在接受你的答案之前会坚持一段时间:) +1
  • 嗨@SandeepBhandari,你有关于“这应该像这样使用吗?”的答案?我真的和你几个月前在同一个角落。我是否每次都复制 BeahviorRelay 对象以接受它?谢谢。
  • 如何删除成员?
  • 创建新数组并发出一个全新数组与仅追加到现有数组末尾相比,性能有何影响?
  • @daltonclaybrook 作为后续,我使用一个包含 1,000,000 个整数的数组进行了测试,并附加了一个包含 1,000,000 个整数的数组。然后我将 2 个数组组合成一个新数组。使用 array1 + array2 与使用 append 创建新数组的时间差异约为 70%。好消息是,即使有这么大的阵列,时间也不到 2 秒。可能不会对我的特定设计产生太大影响。
【解决方案2】:

Dalton's answer 为基础,这是一个方便的扩展:

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

【讨论】:

  • 我个人不想在操作员下隐藏类似的东西——因为这不是你通常应该做的事情。如果你这样做,我会说你应该尽可能明确。
  • @ShaiMishali 为什么你不应该经常这样做?
【解决方案3】:

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

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's answer

还要注意index 的类型是Element.Index,而不是Int 或其他类型。

【讨论】:

    【解决方案4】:

    我创建了这些扩展,有两种方法可以方便迁移,以防你有一个数组变量并且你必须使用追加。

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

    你这样称呼它

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

    【讨论】:

      【解决方案5】:

      我会做这样的事情-

      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)
      

      【讨论】:

      • 感谢您的努力和时间 :) 因此 +1。但是想法是当用户向下滚动collectionView时调用分页的Web服务调用,所以我不能真正将解析的绑定到ui,我需要一个变量/可观察的将数据保存到UI并且我的Web服务调用应该只更新具有新值的变量。所以变量是完美的。现在 BehaviorRelay 做同样的事情,但会带走每次解析新元素时触发 UI 的好处。
      【解决方案6】:

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

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

      【讨论】:

      • IMO,这应该包含在 RxSwift 中。再说一次,BehaviorRelay 也应该如此,但你去吧。
      【解决方案7】:

      关于变量曾经有:

      let variable = Variable("Hello RxSwift")
      variable.value = "Change text" 
      print(variable.value) // "Change text"
      

      在 BehaviorRelay 上你必须使用:

      let relay = BehaviorRelay(value: "Hello RxSwift")
      relay.accept("Change text")
      print(relay.value) // "Change text"
      

      【讨论】:

        【解决方案8】:

        这种扩展怎么样:

        extension BehaviorRelay {
        
            var val: Element {
                get { value }
                set { accept(newValue) }
            }
        }
        

        然后像使用Variable 一样使用它,但不要调用value,而是调用val

        myFilter.val.append(newModel)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-03-09
          • 2023-04-02
          相关资源
          最近更新 更多