The Realm Swift documentation states that most of the properties you use on a model class can be observed using KVO. Using ReactiveCocoa, for every property I have on a model class, I create a comparable rac_
prefixed property that sends value changes which I can then use to bind to views in an MVVM style architecture.
An example model class could look like:
class Post: Object {
dynamic var text = ""
private(set) lazy var rac_text: AnyProperty<String> = { [unowned self] in
return AnyProperty(initialValue: self.name, signal: self.rac_valuesForKeyPath("text", observer: self).toSignal().takeUntil(self.willDeallocSignal())
}()
}
This is incredibly handy becuase 1) it's not mutable from the outside (AnyProperty
vs MutableProperty
and 2) only lives as long as the model does with .takeUntil(self.willDeallocSignal())
. (I also wanted to ask, is the [unowned self]
bit necessary here? I'm not sure if self
gets captured or not, always been bad at that).
The problem comes in with List
properties. Lists can't be marked dynamic which makes sense, their type can't be represented in objective-c. And key value observing works just fine, with one major caveat.
Take the same class with a relationship list property:
class Post: Object {
let users: List<User> = List<User>()
}
The corresponding reactive observation property should look something along the lines of:
private(set) lazy var rac_users: AnyProperty<List<User>> = {
return AnyProperty(initialValue: self.users, signal: self.rac_valuesForKeyPath("users", observer: self).toSignal().takeUntil(self.willDeallocSignal()))
}()
However on observation, the signal doesn't emit List
objects, it emits RLMArray
objects. I've had to jerryrig a signal producer that looks like:
private(set) lazy var rac_posts: AnyProperty<List<Post>> = { [unowned self] in
return AnyProperty<List<Post>>(initialValue: self.posts, producer: self.rac_valuesForKeyPath("posts", observer: self)
.toSignalProducer()
.assumeNoErrors()
.map { $0 as! RLMArray }
.map { array in
var list = List<Post>()
for i in 0..<array.count {
if let element = array[i] as? Post {
list.append(element)
}
}
return list
})
}()
Of course, the if let
statement always fails because RLMObject
can't be cast to Post
. So I either need a) a way to convert RLMObject
s to Object
s or b) a way to kvo on a list property that emits a list. I tested it using traditional KVO and got the same results.
You can observe properties of type List
with Realm Swift by using addNotificationBlock
.
This method takes a closure, which is called on every change. At least as long as you don't block the run loop, then notifications might come coalesced. The initial value is reported as well via this mechanism, so you might get additional signals through that, which you might not expect.
You should be able to wire up this with Reactive Cocoa like seen below:
private(set) lazy var rac_posts: AnyProperty<List<Post>> = { [unowned self] in
return AnyProperty<List<Post>>(initialValue: self.posts, signal: Signal<List<Post>>() { [unowned self] observer in
let notificationToken = self.posts.addNotificationBlock { list in
observer.sendNext(list)
}
return ActionDisposable() {
notificationToken.stop()
}
}
}()
Once #3359 is merged, you will also get fine-grained notifications, which will inform you about the detailed change within the list.