观察字符串并使用RxSwift从API获取

时间:2017-03-01 08:37:09

标签: swift mvvm rx-swift rx-cocoa

我有一个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实现呢?

1 个答案:

答案 0 :(得分:1)

- 编辑

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

这是设置视图模型的更好方法。你没有提供所有必要的信息,所以我采取了一些自由......

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

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