观察一个字符串并使用 RxSwift 从 API 获取
Observe a string and get from API with RxSwift
我有一个 MVVM 测试项目来试验 RxSwift。我有一个 UItextfield 一个按钮。用户写下食物名称,单击按钮并触发从 API 中获取以获取包含该食物的所有食谱。
查看模型
struct FoodViewModel
var foodIdentifier: Variable<String> = Variable<String>("")
init() {
foodIdentifier.asObservable().subscribe(onNext: { (identifier) in
self.getRecipes() // Get from API
})
}
}
ViewController
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 实现?
-- 编辑
我写了以下所有内容,但忘了回答您的明确问题...您收到错误的原因是因为您试图在 self 是结构的闭包中捕获 self 。如果允许这样做,您将捕获甚至还没有完成构建的视图模型的 copy。将视图模型切换为 class 可以缓解问题,因为您不再捕获副本,而是捕获对象本身供以后使用。
这是设置视图模型的更好方法。你没有提供所有必要的信息所以我采取了一些自由...
首先我们需要一个模型。我不知道食谱中应该包含什么,所以你必须填写它。
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: [=11=]) })
}
}
在实际代码中,您不会希望在用户每次键入字母时都进行新的服务器调用。网络上有很多示例解释了如何构建延迟,等待用户在拨打电话之前停止输入。
那么你就有了实际的视图控制器。你没有提到你想用服务器调用的结果做什么。也许您想将结果绑定到 table 视图?我只是在这里打印结果。
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([=12=])
}).disposed(by: bag)
}
var foodText: Observable<String> {
return foodTextField.rx.text.map { [=12=] ?? "" }.asObservable()
}
let bag = DisposeBag()
}
请注意我们如何避免必须执行 IBAction。当您使用 Rx 编写视图控制器时,您会发现几乎所有代码都在 viewDidLoad 方法中结束。这是因为使用 Rx,你主要只是担心将所有东西连接起来。一旦可观察对象连接起来,用户操作就会导致事情发生。这更像是对电子表格进行编程。您只需将公式和 link 可观察值放在一起。用户的数据输入负责实际操作。
以上只是设置所有内容的一种方法。此方法与 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: [=13=]) })
return FoodSink(recipes: recipes)
}
一个要点...尽量避免使用 Subjects
或 Variables
。这是一篇很棒的文章,可帮助确定何时使用主题或变量是合适的:http://davesexton.com/blog/post/To-Use-Subject-Or-Not-To-Use-Subject.aspx
我有一个 MVVM 测试项目来试验 RxSwift。我有一个 UItextfield 一个按钮。用户写下食物名称,单击按钮并触发从 API 中获取以获取包含该食物的所有食谱。
查看模型
struct FoodViewModel
var foodIdentifier: Variable<String> = Variable<String>("")
init() {
foodIdentifier.asObservable().subscribe(onNext: { (identifier) in
self.getRecipes() // Get from API
})
}
}
ViewController
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 实现?
-- 编辑
我写了以下所有内容,但忘了回答您的明确问题...您收到错误的原因是因为您试图在 self 是结构的闭包中捕获 self 。如果允许这样做,您将捕获甚至还没有完成构建的视图模型的 copy。将视图模型切换为 class 可以缓解问题,因为您不再捕获副本,而是捕获对象本身供以后使用。
这是设置视图模型的更好方法。你没有提供所有必要的信息所以我采取了一些自由...
首先我们需要一个模型。我不知道食谱中应该包含什么,所以你必须填写它。
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: [=11=]) })
}
}
在实际代码中,您不会希望在用户每次键入字母时都进行新的服务器调用。网络上有很多示例解释了如何构建延迟,等待用户在拨打电话之前停止输入。
那么你就有了实际的视图控制器。你没有提到你想用服务器调用的结果做什么。也许您想将结果绑定到 table 视图?我只是在这里打印结果。
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([=12=])
}).disposed(by: bag)
}
var foodText: Observable<String> {
return foodTextField.rx.text.map { [=12=] ?? "" }.asObservable()
}
let bag = DisposeBag()
}
请注意我们如何避免必须执行 IBAction。当您使用 Rx 编写视图控制器时,您会发现几乎所有代码都在 viewDidLoad 方法中结束。这是因为使用 Rx,你主要只是担心将所有东西连接起来。一旦可观察对象连接起来,用户操作就会导致事情发生。这更像是对电子表格进行编程。您只需将公式和 link 可观察值放在一起。用户的数据输入负责实际操作。
以上只是设置所有内容的一种方法。此方法与 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: [=13=]) })
return FoodSink(recipes: recipes)
}
一个要点...尽量避免使用 Subjects
或 Variables
。这是一篇很棒的文章,可帮助确定何时使用主题或变量是合适的:http://davesexton.com/blog/post/To-Use-Subject-Or-Not-To-Use-Subject.aspx