I'm building an app using MVVM and ReactiveCocoa to do bindings between the viewModel and the UI, however the view model validation signal subscribe block is not getting called.
My view model is pretty simple and barebones:
class ViewModel: RVMViewModel {
var name: String = "" {
willSet {
println("New Value: \(newValue)")
}
}
required init(){
super.init()
let signal = self.rac_valuesForKeyPath("name", observer: self)
signal.subscribeNext {
println("Subscribe block: \($0)")
}
}
}
In my view controller, I have the following bindings:
//observe ui and programatic changes
RACSignal.merge([self.nameField.racTextSignal(), self.nameField.rac_valuesForKeyPath("text", observer:self)]).subscribeNext({
(next) -> Void in
if let text = next as? String {
self.viewModel.name = text
}
})
RAC(self.nameField, "text") = self.viewModel.rac_valuesForKeyPath("name", observer: self)
I got the RAC macro working in swift based off what I read here.
Now, in my view bindings in my view controller, the subscribeNext blocks are called just fine. In my viewModel, in willSet, the new value prints out. HOWEVER, the subscribe block on my signal in my init block is only being called once, when the property is first initialized. This is driving me up a wall, anyone have any ideas?
I found a solution after a bunch of experimenting. By assigning a signal directly to the view model property, the subscribe block is called every time the value changes.
So instead of doing this:
RACSignal.merge([self.nameField.racTextSignal(), self.nameField.rac_valuesForKeyPath("text", observer:self)]).subscribeNext({
(next) -> Void in
if let text = next as? String {
self.viewModel.name = text
}
})
I did this:
RAC(self.viewModel, "name") <~ RACSignal.merge([self.nameField.racTextSignal(),
self.nameField.rac_valuesForKeyPath("text", observer:self)])
I used this link to get the RAC and <~ to work in swift.
I do not have a solution yet - I am away from my laptop till evening. However, try making signal in the global scope or an instance variable... If that doesn't work, try it on a singleton as a method you explicitly call ... These are more tests but if you tell me how it goes we can work it out together.
A better solution than the one that's accepted is to simply mark the property dynamic:
dynamic var name: String = "" {
willSet {
println("New Value: \(newValue)")
}
}
This enables Obj-C level KVO which is typically disabled for Swift only properties.
Related
In View Controller
viewModel.frequentDate = Date() // This changes frequently
In ViewModel
struct ViewModel {
var frequentDate = Date()
mutating func validateSomeTime(startDatePicker: Observable<Date>) {
self.validdate = startDatePicker.map(someValid)
}
func someValid(_ date: Date) -> String {
if date = self.frequentDate {
return "Done"
}
}
}
frequentDate doesn't change frequently it always give first initial date. When I call observable validate the date value is initial one not updates the latest.
this self.frequentDate will never change of the VC lift cycle.
But if I changed ViewModel struct to class. It works fine
#Paulw11 mentioned a good point. You should probably make your viewModel a class and control its lifecycle in ViewController. The simplest way to do it is to instantiate your ViewModel directly inside ViewController
class ChurchesViewController {
let viewModel = ChurchesViewModel()
}
But what to do if your ViewModel uses some repositories, managers etc.? Well, you should inject ViewModel into ViewController somewhere else. In a simplest architecture such as MVVM-C you will probably use Coordinator to construct your flow:
class ChurchesCoordinator: Coordinator {
init(navigationController: UINavigationController, firstManager: FirstManager, secondManager: SecondManager) { ... }
func start() {
let vc = ChurchesViewController() // any instantiation method
let vm = ChurchesViewModel(firstManager: self.firstManager, secondManager: self.secondManager)
vc.viewModel = vm
navigationController.pushViewController(vc, animated: true, completion: nil)
}
}
This is a simple example, but I personally use a library for DI called Dip or Swinject, it simplifies things a lot. If you to choose other architectures, probably you will have another modules to make a DI.
I have a view structured like a form that creates a model object. I am trying to bind the form elements (UIControl) to the model properties, so that the views auto-update when their corresponding model property is changed, and the model update when the controls are changed (two way binding). The model can change without the view knowing because multiple views can be linked to one same model property.
Approach 1: Plain Swift
My problem is the following: to observe changes to the model properties, I tried to use KVO in Swift, and specifically the observe(_:changeHandler:) method.
class Binding<View: NSObject, Object: NSObject, ValueType> {
weak var object: Object?
weak var view: View?
var objectToViewObservation: NSKeyValueObservation?
var viewToObjectObservation: NSKeyValueObservation?
private var objectKeyPath: WritableKeyPath<Object, ValueType>
private var viewKeyPath: WritableKeyPath<View, ValueType>
init(betweenObject objectKeyPath: WritableKeyPath<Object, ValueType>,
andView viewKeyPath: WritableKeyPath<View, ValueType>) {
self.objectKeyPath = objectKeyPath
self.viewKeyPath = viewKeyPath
}
override func bind(_ object: Object, with view: View) {
super.bind(object, with: view)
self.object = object
self.view = view
// initial value from object to view
self.view![keyPath: viewKeyPath] = self.object![keyPath: objectKeyPath]
// object --> view
objectToViewObservation = object.observe(objectKeyPath) { _, change in
guard var view = self.view else {
// view doesn't exist anymore
self.objectToViewObservation = nil
return
}
guard let value = change.newValue else { return }
view[keyPath: self.viewKeyPath] = value
}
// view --> object
viewToObjectObservation = view.observe(viewKeyPath) { _, change in
guard var object = self.object else {
// object doesn't exist anymore
self.viewToObjectObservation = nil
return
}
guard let value = change.newValue else { return }
object[keyPath: self.objectKeyPath] = value
}
}
}
However some of the properties of my model have types CustomEnum, CustomClass, Bool?, and ClosedRange<Int>, and to use observe I had to mark them as #objc dynamic, which yielded the error:
Property cannot be marked #objc because its type cannot be represented in Objective-C
Approach 2: Using RxSwift rx.observe
I turned to RxSwift and the rx.observe method thinking I could work around this problem, but the same thing happened (at runtime this time).
// In some generic bridge class between the view and the model
func bind(to object: SomeObjectType) {
object.rx
.observe(SomeType.self, "someProperty")
.flatMap { Observable.from(optional: $0) }
.bind(to: self.controlProperty)
.disposed(by: disposeBag)
}
Approach 3: Using RxSwift BehaviorRelays?
This is my first experience with RxSwift, and I know I should be using BehaviorRelay for my model, however I don't want to change all my model properties as my model object is working with other framework. I could try to implement a bridge then, to transform model properties into BehaviorRelay, but I would come across the same problem: how to listen for model changes.
In this question, there were no answer as to how to listen for property changes without refactoring all model properties to RxSwift's Variable (currently deprecated).
Approach 4: Using didSet Swift property observer?
The didSet and willSet property observers in plain Swift would allow listening for changes, however this would require to mark all the properties in the model with these observers, which I find quite inconvenient, since my model object has a lot of properties. If there is a way to add these observers at runtime, this would solve my problem.
I believe that what I am trying to achieve is quite common, having a set of views that modify a model object, however I can't find a way to properly link the model to the view, so that both auto-update when needed.
Basically, I'm looking for an answer to one of the following questions:
Is there something I overlooked, is there a better way to achieve what I want?
or How to overcome the "Property cannot be marked #objc" problem?
or How to bridge my model object to BehaviorRelay without changing my model?
or How to add didSet observers at runtime?
You said:
I believe that what I am trying to achieve is quite common, having a set of views that modify a model object, however I can't find a way to properly link the model to the view, so that both auto-update when needed.
Actually it's not at all common. One idea you don't mention is to wrap your entire model into a Behavior Relay. Then the set of views can modify your model object.
Each of your views, in turn, can observe the model in the behavior relay and update accordingly. This is the basis of, for example, the Redux pattern.
You could also use your approach #3 and use property wrappers to make the code a bit cleaner:
#propertyWrapper
struct RxPublished<Value> {
private let relay: BehaviorRelay<Value>
public init(wrappedValue: Value) {
self.relay = BehaviorRelay(value: wrappedValue)
}
var wrappedValue: Value {
get { relay.value }
set { relay.accept(newValue) }
}
var projectedValue: Observable<Value> {
relay.asObservable()
}
}
But understand that the whole reason you are having this problem is not due to Rx itself, but rather due to the fact that you are trying to mix paradigms. You are increasing the complexity of your code. Hopefully, it's just a temporary increase during a refactoring.
Old Answer
You said you want to make it "so that the views auto-update when their corresponding model property is changed, and the model update when the controls are changed (two way binding)."
IMO, that way of thinking about the problem is incorrect. Better would be to examine each output independently of all other outputs and deal with it directly. In order to explain what I mean, I will use the example of converting °F to °C and back...
This sounds like a great reason to use 2-way binding but let's see?
// the chain of observables represents a view model
celsiusTextField.rx.text // • this is the input view
.orEmpty // • these next two convert
.compactMap { Double($0) } // the view into an input model
.map { $0 * 9 / 5 + 32 } // • this is the model
.map { "\($0)" } // • this converts the model into a view
.bind(to: fahrenheitTextField) // • this is the output view
.disposed(by: disposeBag)
fahrenheitTextField.rx.text
.orEmpty
.compactMap { Double($0) }
.map { ($0 - 32) * 5 / 9 }
.map { "\($0)" }
.bind(to: celsiusTextField.rx.text)
.disposed(by: disposeBag)
The above code handles the two-way communication between the text fields without two-way binding. It does this by using two separate view models (The view model is the code between the text Observable and the text Observer as described in the comments.)
We can see a lot of duplication. We can DRY it up a bit:
extension ControlProperty where PropertyType == String? {
func viewModel(model: #escaping (Double) -> Double) -> Observable<String> {
orEmpty
.compactMap { Double($0) }
.map(model)
.map { "\($0)" }
}
}
You may prefer a different error handling strategy than what I used above. I was striving for simplicity since this is an example.
The key though is that each observable chain should be centered on a particular effect. It should combine all the causes that contribute to that effect, implement some sort of logic on the inputs, and then emit the needed output for that effect. If you do this to each output individually you will find that you don't need two-way binding at all.
I know state is the enemy of Reactive programming but I'm dealing with it in my process of learning RxSwift.
My app is very simple, the first screen is a list and a search of books and the second a detail of the book in which you can add/remove a book to your shelf and mark it as read/unread.
To show the detail of the book I create a BookViewModel passing a BooksService to perform network operations and the current Book to show.
The problem is that I have to keep track of the changes in the book in order to change the UI: for example, after removing the book the button that previously says "Remove" now it has to say "Add".
I achieve this behavior using a Variable<Book> exposed to the observers as a Driver<Book>, but I'm messing a lot with it when the network operation returns and I have to update the value of the Variable<Book> in order to trigger the update of the UI.
This is the initializer of the view model:
init(book: Book, booksService: BooksService) {
self._book = Variable(book)
self.booksService = booksService
}
This is the observable I expose
var book: Driver<Book> {
return _book.asDriver()
}
And here it is my function to add/remove the book:
func set(toggleInShelfTrigger: Observable<Void>) {
toggleInShelfTrigger // An observable from a UIBarButtonItem tap
.map({ self._book.value }) // I have to map the variable's value to the actual book
.flatMap({ [booksService] book -> Observable<Book> in
return (book.isInShelf ?
booksService.delete(book: book) :
booksService.add(book: book))
}) // Here I have to know if the books is in the shelf or not in order to perform one operation or another.
.subscribe(onNext: { self._book.value = $0 }) // I have to update the variable's value in order to trigger the UI update
.disposed(by: disposeBag)
}
I am very unhappy with this code and the whole view model. It works but it is clunky, and essentially wrong because if the network operation fails the subscription will be disposed and my button will became unresponsive.
If I get rid of the Variable<Book> and return a Driver<Book> from the method set(toggleInShelfTrigger: Observable<Void>) I won't have this mess but I will not be able to know if I have to add or to remove the book.
So, what is the real world way to keep track of the state of an object in this kind of app? How can I achieve this by only using Rx operators?
EDIT
I've managed to clean that crappy code but I'm still trying to achieve state without Variable and using scan operator.
This is my new BookViewModel initializer:
init(book: Book, booksService: BooksService) {
self.bookVariable = Variable(book)
let addResult = addBook
.mapBookFrom(bookVariable)
.flatMapLatest({ booksService.add(book: $0) })
.updateBookVariable(bookVariable)
let removeResult = ... // Similar to addResult, changing service call
let markReadResult = ... // Similar to addResult, changing service call
let markUnreadResult = ... // Similar to addResult, changing service call
self.book = Observable.of(addResult, removeResult, markReadResult, markUnreadResult).merge()
.startWith(.success(book))
}
I made a couple of custom operators to help me manage the Variable<Book>, one to get the real Book:
private extension ObservableType where E == Void {
func mapBookFrom(_ variable: Variable<Book>) -> Observable<Book> {
return map({ _ in return variable.value })
}
}
And another to update the Variable after the service returns:
private extension ObservableType where E == BookResult<Book> {
func updateBookVariable(_ variable: Variable<Book>) -> Observable<BookResult<Book>> {
return self.do(onNext: { result in
if case let .success(book) = result {
variable.value = book
}
})
}
}
Now I have a very clean view model, but not "perfect".
I would place the responsibility of the observing changes to the model object (Book) with the View.
Also, Variable is deprecated, best to use PublishRelay instead.
Of course, it depends how far you want to engineer this architecture, but something not too far from your example would be:
class BookDetailViewController: UIViewController {
let viewModel = BookViewModel(book: Book, booksService: BooksService)
func loadView() {
view = BookDetailView(viewModel: viewModel)
}
// ...
}
class BookDetailViewModel {
let book: PublishRelay<Book>
func addBook() {
book
.flatMap(booksService.add)
.bind(to: book)
.subscribe()
}
// ...
}
class BookDetailView: UIView {
let button: UIButton
init(viewModel: BookDetailViewModel) {
viewModel.book
.asObservable()
.subscribe(onNext: { book [button] in
button.setText(book.isSaved ? "Remove" : "Add")
})
button.rx.tap
.map { _ in viewModel.book.isSaved }
.subscribe(onNext: {
$0 ? viewModel.removeBook() : viewModel.addBook()
})
}
}
You could also implement a func toggle() in the view model instead, and just forward the button tap to call that method. It might be more accurate, semantically, depending on your interpretation of business logic and the extent to which you want to gather all of it in the view model.
Also note the example code is missing dispose bags, but that's another topic.
This is a very specific and long question, but I'm not smart enough to figure it out by myself..
I was very intrigued by this YouTube-video from raywenderlich.com, which uses the "boxing" method to observe a value.
Their Box looks like this:
class Box<T> {
typealias Listener = T -> Void
var listener: Listener?
var value: T {
didSet {
listener?(value)
}
init(_ value: T) {
self.value = value
}
func bind(listener: Listener?) {
self.listener = listener
listener?(value)
}
}
It's apparent that only one listener is allowed per "box".
let bindable:Box<String> = Box("Some text")
bindable.bind { (text) in
//This will only be called once (from initial setup)
}
bindable.bind { (text) in
// - because this listener has replaced it. This one will be called when value changes.
}
Whenever a bind like this is set up, the previous binds would be disposed, because Box replaces the listener with the new listener.
I need to be able to observe the same value from different places. I have reworked the Box like this:
class Box<T> {
typealias Listener = (T) -> Void
var listeners:[Listener?] = []
var value:T{
didSet{
listeners.forEach({$0?(value)})
}
}
init(_ value:T){
self.value = value
}
func bind(listener:Listener?){
self.listeners.append(listener)
listener?(value)
}
}
However - this is also giving me trouble, obviously.. There are places where I want the new binding to remove the old binding. For example, if I observe a value in an object from a reusable UITableViewCell, it will be bound several times when scrolling. I now need a controlled way to dispose specific bindings.
I attempted to solve this by adding this function to Box:
func freshBind(listener:Listener?){
self.listeners.removeAll()
self.bind(listener)
}
This worked in a way, I could now use freshBind({}) whenever I wanted to remove the old listeners, but this isn't exactly what I want either. I'd have to use this when observing a value from UITableViewCells, but I also need to observe the same value from elsewhere. As soon as a cell was reused, I removed the old observers as well as the other observers I needed.
I am now confident that I need a way to retain a disposable object wherever I would later want to dispose them.
I'm not smart enough to solve this on my own, so I need help.
I've barely used some of the reactive-programming frameworks out there (like ReactiveCocoa), and I now understand why their subscriptions return Disposable objects which I have to retain and dispose of when I need. I need this functionality.
What I want is this: func bind(listener:Listener?)->Disposable{}
My plan was to create a class named Disposable which contained the (optional) listener, and turn listeners:[Listener?] into listeners:[Disposable], but I ran into problems with <T>..
Cannot convert value of type 'Box<String?>.Disposable' to expected argument type 'Box<Any>.Disposable'
Any smart suggestions?
The way I like to solve this problem is to give each observer a unique identifier (like a UUID) and use that to allow the Disposable to remove the observer when it's time. For example:
import Foundation
// A Disposable holds a `dispose` closure and calls it when it is released
class Disposable {
let dispose: () -> Void
init(_ dispose: #escaping () -> Void) { self.dispose = dispose }
deinit { dispose() }
}
class Box<T> {
typealias Listener = (T) -> Void
// Replace your array with a dictionary mapping
// I also made the Observer method mandatory. I don't believe it makes
// sense for it to be optional. I also made it private.
private var listeners: [UUID: Listener] = [:]
var value: T {
didSet {
listeners.values.forEach { $0(value) }
}
}
init(_ value: T){
self.value = value
}
// Now return a Disposable. You'll get a warning if you fail
// to retain it (and it will immediately be destroyed)
func bind(listener: #escaping Listener) -> Disposable {
// UUID is a nice way to create a unique identifier; that's what it's for
let identifier = UUID()
// Keep track of it
self.listeners[identifier] = listener
listener(value)
// And create a Disposable to clean it up later. The Disposable
// doesn't have to know anything about T.
// Note that Disposable has a strong referene to the Box
// This means the Box can't go away until the last observer has been removed
return Disposable { self.listeners.removeValue(forKey: identifier) }
}
}
let b = Box(10)
var disposer: Disposable? = b.bind(listener: { x in print(x)})
b.value = 5
disposer = nil
b.value = 1
// Prints:
// 10
// 5
// (Doesn't print 1)
This can be nicely expanded to concepts like DisposeBag:
// Nothing but an array of Disposables.
class DisposeBag {
private var disposables: [Disposable] = []
func append(_ disposable: Disposable) { disposables.append(disposable) }
}
extension Disposable {
func disposed(by bag: DisposeBag) {
bag.append(self)
}
}
var disposeBag: DisposeBag? = DisposeBag()
b.bind { x in print("bag: \(x)") }
.disposed(by: disposeBag!)
b.value = 100
disposeBag = nil
b.value = 500
// Prints:
// bag: 1
// bag: 100
// (Doesn't print "bag: 500")
It's nice to build some of these things yourself so you get how they work, but for serious projects this often is not really sufficient. The main problem is that it isn't thread-safe, so if something disposes on a background thread, you may corrupt the entire system. You can of course make it thread-safe, and it's not that difficult.
But then you realize you really want to compose listeners. You want a listener that watches another listener and transforms it. So you build map. And then you realize you want to filter cases where the value was set to its old value, so you build a "only send me distinct values" and then you realize you want filter to help you there, and then....
you realize you're rebuilding RxSwift.
This isn't a reason to avoid writing your own at all, and RxSwift includes a ton of features (probably too many features) that many projects never need, but as you go along you should keep asking yourself "should I just switch to RxSwift now?"
I want to write a function to reverse geocode a location and assign the resulting string into a variable. Following this post i've got something like this:
extension CLLocation {
func reverseGeocodeLocation(completion: (answer: String?) -> Void) {
CLGeocoder().reverseGeocodeLocation(self) {
if let error = $1 {
print("[ERROR] \(error.localizedDescription)")
return
}
if let a = $0?.last {
guard let streetName = a.thoroughfare,
let postal = a.postalCode,
let city = a.locality else { return }
completion(answer: "[\(streetName), \(postal) \(city)]")
}
}
}
}
For calling this function i've just got something like this:
location.reverseGeocodeLocation { answer in
print(answer)
}
But instead i want to assign the string value of answer to a variable and i don't know how to pass that data out of the closure. What is the best way to do something like this?
The problem is that it runs asynchronously, so you can't return the value. If you want to update some property or variable, the right place to do that is in the closure you provide to the method, for example:
var geocodeString: String?
location.reverseGeocodeLocation { answer in
geocodeString = answer
// and trigger whatever UI or model update you want here
}
// but not here
The entire purpose of the closure completion handler pattern is that is the preferred way to provide the data that was retrieved asynchronously.
Short answer: You can't. That's not how async programming works. The function reverseGeocodeLocation returns immediately, before the answer is available. At some point in the future the geocode result becomes available, and when that happens the code in your closure gets called. THAT is when you do something with your answer. You could write the closure to install the answer in a label, update a table view, or some other behavior. (I don't remember if the geocoding methods' closures get called on the main thread or a background thread. If they get called on a background thread then you would need to wrap your UI calls in dispatch_async(dispatch_get_main_queue()).)