观察字符串并使用RxSwift从API获取
我有一个MVVM测试项目来测试RxSwift。我有一个UItextfield和一个按钮。用户输入一个食物名称,点击按钮,就会触发一个来自API的get来获取该食物的所有食谱 查看模型观察字符串并使用RxSwift从API获取,swift,mvvm,rx-swift,rx-cocoa,Swift,Mvvm,Rx Swift,Rx Cocoa,我有一个MVVM测试项目来测试RxSwift。我有一个UItextfield和一个按钮。用户输入一个食物名称,点击按钮,就会触发一个来自API的get来获取该食物的所有食谱 查看模型 struct FoodViewModel var foodIdentifier: Variable<String> = Variable<String>("") init() { foodIdentifier.asObservable().subscr
struct FoodViewModel
var foodIdentifier: Variable<String> = Variable<String>("")
init() {
foodIdentifier.asObservable().subscribe(onNext: { (identifier) in
self.getRecipes() // Get from API
})
}
}
编译之后,我得到了一个错误
Closure cannot implicitly capture a mutating self parameter
我做错了什么?我想这是因为FoodViewModel的结构。如果是,如何使用struct实现该功能?--EDIT
我写了下面的所有内容,但忘了回答你明确的问题。。。出现错误的原因是,您试图在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) })
}
}
协议API{
func getRecipies(带食物:字符串)->可观察
}
协议食物源{
var foodText:Observable{get}
}
结构FoodViewModel{
让食谱:可观察
init(api:api,源代码: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()
}
类FoodViewController:UIViewController,FoodSource{
@IBOutlet弱var foodTextField:UITextField!
var-api:api!
重写func viewDidLoad(){
super.viewDidLoad()
让viewModel=FoodViewModel(api:api,来源:self)
viewModel.recipes.subscribe(onNext:{
印刷品(0美元)
}).处置(由:袋)
}
var foodText:可观察{
返回foodTextField.rx.text.map{$0??“}.asObservable()
}
let bag=DisposeBag()
}
注意我们如何避免进行iAction。当您使用Rx对视图控制器进行编码时,您会发现几乎所有的代码都以viewDidLoad方法结束。这是因为使用Rx时,您主要担心的是接线问题。一旦可观察对象连接起来,用户操作将导致事情发生。这更像是编写电子表格。你只需要输入公式,把观测值联系起来。用户的数据输入负责实际操作
以上只是设置一切的一种方式。该方法与Srdan Rasic的模型非常匹配:
您还可以将food view模型转换为纯函数,如下所示:
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)
}
struct FoodSink{
让食谱:可观察
}
func foodViewModel(api:api,source:FoodSource)->FoodSink{
让recipes=source.foodText
.flatMapLatest({api.getRecipies(withFood:$0)})
返回食物链接(配方:配方)
}
一个外卖从这个。。。尽量避免使用
主题
或变量
。这里有一篇很好的文章,可以帮助您确定何时使用主题或变量是合适的:您在哪里实例化您的VM?我认为您不应该将订阅直接放在viewModel的init中。在订阅的VM中应该有一个方法listenToFoodIdentifier,在VC中实例化VM后,调用viewModel.listenToFoodIdentifier()原因是结构是值类型,闭包的实例将获得其自己的、捕获值的独立副本,只有它可以更改。这个值是在执行闭包时捕获的。这个答案将帮助您:为什么要如此避免类?要使用Rx闭包,您需要获取订阅对象的引用(通常使用[unowned self]避免强引用循环),而不是该对象的值,因此类不是structsWow!谢谢,这就是我需要的答案:)只是一个小问题,我不明白FoodSource的用途它是TableView数据源?如果是,为什么是字符串?关于按钮,我希望字符串仅在用户单击按钮时发送到API。FoodSource
是表示用户输入的协议。视图控制器通过将foodText
绑定到foodTextField
来实现该协议。要连接到按钮,您需要向FoodSource
协议添加一个可观察的
,以表示按钮,将observable绑定到视图控制器中的特定按钮,然后使用视图模型的init
方法中的sample
操作符,确保文本仅在点击按钮时通过管道。@DanielT。我遵循上面的方法,但是如何获得可观察的
s的当前值(我在发出API请求时需要它)?我不需要变量
而不是可观察的
?像上面那样使用平面图。
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)
}