-- EDIT
I wrote all of the below but forgot to answer your explicit question... The reason you are getting the error is because you are trying to capture self in a closure where self is a struct. If this were allowed, you would be capturing a copy of the view model that you haven't even finished constructing. Switching your view model to a class alleviates the problem because you are no longer capturing a copy, but the object itself for later use.
Here is a better way to set up a view model. You didn't give all the necessary information so I took some liberties...
First we need a model. I don't know exactly what should be in a Recipe so you will have to fill it in.
struct Recipe { }
Next we have our view model. Note that it doesn't directly connect with anything in the UI or the server. This makes testing very easy.
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) })
}
}
In real code, you aren't going to want to make a new server call every time the user types a letter. There are a lot of examples on the web that explain how to build in a delay that waits until the user stops typing before making the call.
Then you have the actual view controller. You didn't mention what you wanted to do with the results of the server call. Maybe you want to bind the result to a table view? I'm just printing the results here.
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()
}
Notice how we avoid having to make an IBAction. when you are coding up a view controller with Rx, you will find that almost all the code ends up in the viewDidLoad method. This is because with Rx, you are mainly just worried about wiring everything up. Once the observables are wired up, user action will cause things to happen. It's more like programming a spreadsheet. You just put in the formulas and link the observables together. User's data entry takes care of the actual action.
The above is just one way of setting everything up. This method matches closely with Srdan Rasic's model: http://rasic.info/a-different-take-on-mvvm-with-swift/
You could also turn the food view model into a pure function like this:
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)
}
One takeaway from this... Try to avoid using Subjects
or Variables
. Here's a great article that helps determine when using a Subject or Variable is appropriate: http://davesexton.com/blog/post/To-Use-Subject-Or-Not-To-Use-Subject.aspx