【问题标题】:Observe a string and get from API with RxSwift使用 RxSwift 观察字符串并从 API 获取
【发布时间】:2017-07-20 11:51:00
【问题描述】:

我有一个 MVVM 测试项目来试验 RxSwift。我有一个 UItextfield 一个按钮。用户输入食物名称,单击按钮,触发从 API 获取以获取该食物的所有食谱。

查看模型

struct FoodViewModel
    var foodIdentifier: Variable<String> = Variable<String>("")
    init() {    
        foodIdentifier.asObservable().subscribe(onNext: { (identifier) in
            self.getRecipes() // Get from API
        })
    }
}

视图控制器

class FoodViewController: UIViewController {
    @IBOutlet weak var foodTextField: UITextField!

    @IBAction func setCurrentRace(_ sender: Any) {
        viewModel.foodIdentifier.value = foodTextField.text!
    }
}

编译后报错

Closure cannot implicitly capture a mutating self parameter

我做错了什么?我认为这是因为 FoodViewModel 的结构。如果是,我该如何使用 struct 来实现?

【问题讨论】:

  • 你在哪里实例化你的虚拟机?
  • 我认为您不应该将订阅直接放在 viewModel 的 init 中。您应该在带有订阅的 VM 中拥有一个方法 listenToFoodIdentifier,在您实例化 VM 后,在您的 VC 中调用 viewModel.listenToFoodIdentifier()
  • 原因是结构是值类型,闭包的实例将获得自己的、独立的捕获值的副本,并且只有它可以更改。该值是在执行闭包时捕获的。
  • 这个答案会对你有所帮助:stackoverflow.com/questions/41940994/…
  • 你为什么这么想避课?要使用 Rx 闭包,您需要获取您订阅的对象的引用(通常您避免使用 [unowned self] 的强引用循环)而不是该对象的值,因此类不是结构

标签: swift mvvm rx-swift rx-cocoa


【解决方案1】:

-- 编辑

我写了以下所有内容,但忘记回答您的明确问题...您收到错误的原因是因为您试图在 self 是一个结构的闭包中捕获 self 。如果允许这样做,您将捕获您甚至还没有完成构建的视图模型的副本。将视图模型切换到类可以缓解问题,因为您不再捕获副本,而是捕获对象本身以供以后使用。


这是设置视图模型的更好方法。你没有提供所有必要的信息,所以我冒昧了......

首先我们需要一个模型。我不确切知道食谱中应该包含什么,所以你必须填写它。

struct Recipe { }

接下来我们有我们的视图模型。请注意,它不直接连接 UI 或服务器中的任何内容。这使得测试变得非常容易。

protocol API {
    func getRecipies(withFood: String) -> Observable<[Recipe]>
}

protocol FoodSource {
    var foodText: Observable<String> { get }
}

struct FoodViewModel {

    let recipes: Observable<[Recipe]>

    init(api: API, source: FoodSource) {
        recipes = source.foodText
            .flatMapLatest({ api.getRecipies(withFood: $0) })
    }
}

在实际代码中,您不会希望每次用户键入字母时都进行新的服务器调用。网络上有很多示例说明了如何构建延迟,等待用户停止输入后再拨打电话。

然后你就有了实际的视图控制器。你没有提到你想对服务器调用的结果做什么。也许您想将结果绑定到表格视图?我只是在这里打印结果。

class FoodViewController: UIViewController, FoodSource {

    @IBOutlet weak var foodTextField: UITextField!

    var api: API!

    override func viewDidLoad() {
        super.viewDidLoad()
        let viewModel = FoodViewModel(api: api, source: self)
        viewModel.recipes.subscribe(onNext: {
            print($0)
        }).disposed(by: bag)
    }

    var foodText: Observable<String> {
        return foodTextField.rx.text.map { $0 ?? "" }.asObservable()
    }

    let bag = DisposeBag()
}

请注意我们如何避免进行 IBAction。当您使用 Rx 编写视图控制器时,您会发现几乎所有代码都以 viewDidLoad 方法结束。这是因为使用 Rx,您主要只是担心将所有东西连接起来。一旦可观察对象连接起来,用户操作就会导致事情发生。这更像是对电子表格进行编程。您只需输入公式并将可观察值链接在一起。用户的数据输入负责实际操作。

以上只是设置所有内容的一种方法。此方法与 Srdan Rasic 的模型非常匹配:http://rasic.info/a-different-take-on-mvvm-with-swift/

你也可以像这样把食物视图模型变成一个纯函数:

struct FoodSink {
    let recipes: Observable<[Recipe]>
}

func foodViewModel(api: API, source: FoodSource) -> FoodSink {
    let recipes = source.foodText
        .flatMapLatest({ api.getRecipies(withFood: $0) })
    return FoodSink(recipes: recipes)
}

从中得出一个结论...尽量避免使用SubjectsVariables。这是一篇很棒的文章,可以帮助确定何时使用主题或变量是合适的:http://davesexton.com/blog/post/To-Use-Subject-Or-Not-To-Use-Subject.aspx

【讨论】:

  • 哇!谢谢这个答案是我需要的:) 只是一个小问题,我不明白 FoodSource 它是 TableView 数据源的目的吗?如果是,为什么是字符串?关于按钮,我希望仅在用户单击按钮时将字符串发送到 API。
  • FoodSource 是代表用户输入的协议。视图控制器通过将foodText 绑定到foodTextField 来实现该协议。
  • 要连接到一个按钮,你需要在FoodSource 协议中添加一个Observable&lt;Void&gt; 来表示该按钮,将可观察对象绑定到视图控制器中的特定按钮,然后使用@视图模型的 init 方法中的 987654334@ 运算符,以确保文本仅在点击按钮时通过管道。
  • @DanielT。我遵循上述方法,但是如何获取Observables 的当前值(我在发出 API 请求时需要它)?我不需要Variable 而不是Observable 吗?
  • 像我上面那样使用 flatMap。
猜你喜欢
  • 2017-01-30
  • 1970-01-01
  • 2016-06-04
  • 2018-01-15
  • 1970-01-01
  • 1970-01-01
  • 2018-09-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多