在 RxSwift 中接收和发送 observable

Posted

技术标签:

【中文标题】在 RxSwift 中接收和发送 observable【英文标题】:Receive and send observable in RxSwift 【发布时间】:2020-01-23 08:17:11 【问题描述】:

我有现有的工作代码,看起来像这样(删除了不必要的东西):

queryTextField.rx.text.orEmpty
            .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
            .distinctUntilChanged()
            .flatMapLatest  query in
                return try AddFriendViewController.getSearchResults(query)
                    .retry(3)
                    .startWith([])
                    .catchErrorJustReturn([])
            
            .bind(to: tableView.rx.items(cellIdentifier: "cell", cellType: AddFriendTableViewCell.self))  (row, item, cell) in                
                cell.nameLabel.text = "\(item.firstName) \(item.lastName)"
            
            .disposed(by: disposeBag)

当您在文本字段中键入名称时,它会自动搜索(通过 API)匹配此查询的朋友。

但我还想添加另一个功能 - 从联系人导入,由附加按钮调用。我有一个本地联系人列表,并使用它们向 API 询问搜索结果。我想在同一个地方显示这些结果。但我不知道如何将这些结果发送到 tableView 中。我想创建一个变量:

var items: Observable<[FriendSearchResult]> = Observable.just([])

但我不知道如何发送/接收数据。有什么线索吗?

编辑:

感谢@andromedainative,现在代码看起来像这样:

var items: BehaviorRelay<[FriendSearchResult]> = BehaviorRelay(value: [])

items.asObservable()
        .bind(to: tableView.rx.items(cellIdentifier: "cell", cellType: AddFriendTableViewCell.self))  (row, item, cell) in                
            cell.nameLabel.text = "\(item.firstName) \(item.lastName)"
        
        .disposed(by: disposeBag)

queryTextField.rx.text.orEmpty
        .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
        .distinctUntilChanged()
        .flatMapLatest  query in
            return try AddFriendViewController.getSearchResults(query)
                .retry(3)
                .startWith([])
                .catchErrorJustReturn([])
        
        .bind(to: items) //this was a missing piece
        .disposed(by: disposeBag)

从 getContacts 传递结果很简单:

items.accept(data)

但是,我有一个特殊情况,我必须刷新数据。如何调用 queryTextField 来调用同一个查询的 API?

我只找到了一个 hacky 方法:

let query = queryTextField.text ?? ""
queryTextField.text = ""
queryTextField.sendActions(for: .valueChanged)
queryTextField.text = query
queryTextField.sendActions(for: .valueChanged)

我必须更改为其他值并将其更改回来。有没有更干净的解决方案?

【问题讨论】:

【参考方案1】:

您绝对可以使用 rx 将对象绑定到表视图。我将 BehaviorRelay 用于我的数组,因为我希望能够访问项目的 .value 属性。

这是一个非常伪代码分析器,所以它不会编译,但我希望你能明白它的要点:

class MyViewController: UITableViewController 

  private let disposeBag = DisposeBag()

  var myItems: BehaviorRelay<[FeedItem]>

  func bindData() 

    tableView.rx.setDelegate(self).disposed(by: disposeBag)

    myItems.asObservable()
        .bind(to: tableView.rx.items)  [weak self] tableView, row, myItemType in
            guard let `self` = self else  return UITableViewCell() 
            let indexPath = IndexPath(row: row, section: 0)
            let myItem = self.myItems.value[indexPath.row]
            let myCell = tableView.dequeueCell(reuseIdentifier: "MyIdentifier", for: indexPath) as! MyCellType

            return myCell
    .disposed(by: disposeBag)
  


【讨论】:

是的,我明白了,但问题是如何连接这两个案例。 这很简单,只需将另一个按钮调用中的项目的值添加到同一个数组中。所以首先你有你添加到数组中的查询结果。 var queriedResults = [SomeItems] 然后你就有了从你的按钮按下得到的项目。 func handleButtonPress() ... let myNewImportedContacts = [SomeItems] myItems.accept(myItems.value + myNewImportedContacts) // 现在你在同一个表视图数据源中 让我换个方式问这个问题。我应该在 flatMapLatest 之后调用什么来将结果传递给项目?【参考方案2】:

一般来说,当你想组合两个或多个 observable 发出的值时,你需要使用combineLatestzipwithLatestFrommerge 之一(可能与scan 一起使用)。这取决于您应该使用哪个上下文。这篇文章可能会有所帮助:Recipes for Combining Observables in RxSwift

以下是针对您的具体情况的建议。下面的代码添加了几个您需要实现的函数:func getContacts() -&gt; Observable&lt;[Contact]&gt;func getSearchResults(_ contact: Contact) -&gt; Observable&lt;[Friend]&gt;

let singleSearchResult = queryTextField.rx.text.orEmpty
    .debounce(.milliseconds(300), scheduler: MainScheduler.instance)
    .distinctUntilChanged()
    .flatMapLatest  query in
        return try getSearchResults(query)
            .retry(3)
            .startWith([])
            .catchErrorJustReturn([])
    

let contactsSearchResult = checkContactsButton.rx.tap
    .flatMapLatest  getContacts() 
    .flatMapLatest 
        Observable.combineLatest($0.map 
            getSearchResults($0)
                .catchErrorJustReturn([])
        )
        .startWith([])
    
    .map  $0.flatMap  $0  

Observable.merge(singleSearchResult, contactsSearchResult)
    .bind(to: tableView.rx.items(cellIdentifier: "cell", cellType: AddFriendTableViewCell.self))  (row, item, cell) in
        cell.nameLabel.text = "\(item.firstName) \(item.lastName)"

.disposed(by: disposeBag)

顺便说一句,你的getContacts 不应该扔东西。它的错误应该从 Observable 发出,而不是抛出。此外,您应该在文本字段上使用 debounce,而不是节流。后者更适合 Void 类型,而不是字符串。

【讨论】:

很好的提示,谢谢。但是,就我而言,我不想合并数据,而是替换它。正如我在编辑的答案中所述,我刚刚使用了接受(您可能想帮助我解决此设置中出现的另一个问题,请参阅我的编辑)。

以上是关于在 RxSwift 中接收和发送 observable的主要内容,如果未能解决你的问题,请参考以下文章

RxSwift 订阅者接收多个事件

观察者在单元格 RxSwift 中处理后仍然接收事件

RxSwift之深入解析场景特征序列的使用和底层实现

如何在MVVM架构中使用RxSwift发送参数来查看模型?

在 RxSwift 中,我如何对 observable 未发送任何事件进行单元测试?

使用 RxSwift 从 UITableViewCell 发送回调到 UIViewController