UITableViewCell binding to Data Model (updates driven by FetchedResultsController) - ios

I'm attempting to have my individual UITableViewCells: I'd love to have the initial values of the model brought over to their UI representation as well as after the user makes a change have the new UI value brought back to the model. Let's just focus on the latter first: UI changes propagating to the model.
For a little more background understanding, I'm running into an issue when adding a new item. This table is driven by an NSFetchedResultsController which sends one didChangeObject with a ChangeInsert and a second didChangeObject for ChangeUpdate. The ChangeInsert triggers an insertRowsAtIndexPaths on the table and the ChangeUpdate does a reloadRowsAtIndexPath.
Because of these two responses my table view asks for cellForRowAtIndexPath twice. This shouldn't be a problem if the same cell is disposed of properly between requests but it doesn't appear to be: I receive an assert that the property in the model is already bound to a RACSignal! I've tried any number of ways to be more explicit such as:
RAC(self.model,value) = [ [RACSignal merge:#[self.valueField.rac_textSignal] ] takeUntil:self.rac_prepareForReuseSignal]
however the reuse signal does not fire in time as it still asserts (aside, is there a recommended way to directly debug a signal like this firing?)
I've tried adding an additional takeUntil:[RACObserve(self, model) to dispose of the signal as soon as the reused cell's model is overwritten (and have it bind to the new model) and understandably this seems to result in the first value disposing. However adding skip:1 to the observe puts me right back where I am.
Please let me know if there is anywhere else I can add clarity or if you have another way to lay these things out. I'm very new to Reactive Cocoa and still learning best practices :)
Thanks!

Related

Prevent redundant operations in RxSwift

I'm starting my adventure with RxSwift, having small experience with React in js already. I think that my problem is common, but I'm not sure how to describe it in concise abstract way, so instead I will describe it on the example.
I'm building iOS app showing some charts. The part of interest consist of ChartAreaController, ChartInfoController, both embedded in ChartController. First controller is the area showing some graph(based on rx chartData property), and the second one among others will have a slider for user to restrict show x-value (rx selectedXRange property) which is restricted to be between some min and max. The min/max value is defined by the current chart data.
Behavior when slider change updates chart is defined in ChartController:
override func viewDidLoad() {
super.viewDidLoad()
(...)
chartInfoController.selectedXRange.asObservable()
.subscribe(onNext: { [unowned self] selectedXRange in
(...)
let chartData = self.filterChartData(from: self.rawChartData, in: selectedXRange)
self.chartAreaController.chartData.accept(chartData)
}).disposed(by: disposeBag)
The filterChartData() method just filters out data that is not in the range, but for the sake of the argument we can assume it is very costly and I don't want it to run twice when it is not necessary.
When user changes the chart he or she wants to show, the new data arrives from server (again ChartController):
private func handleNewData(_ rawChartData: ChartData) {
self.rawChartData = rawChartData
guard let allowedXRange = rawChartData.xRange() else { return }
let selectedXRange = chartInfoController.selectedXRange.value
let newSelectedXRange = calculateSelectedXRange(currentSelectedDays: selectedDaysRange, availableDaysRange: daysRange)
let chartData = filterChartData(from: rawChartData, in: selectedXRange)
self.chartInfoController.allowedXRange = allowedXRange //this line is not crucial
self.chartInfoController.selectedXRange.accept(newSelectedXRange)
self.chartAreaController.chartData.accept(rawChartData)
}
So upon new chart data arrival it may be the case that the currently selected xRange must be trimmed because of the new min/max values of the data. So the side effect of the method will be changing the selectedXRange and in turn running the subscription I pasted earlier. So when new data arrives the chartData is updated twice and I don't want it to happen.
Of course I can comment out last line of the handleNewData() method, but I don't like it very much, since main reason for existence of the handleNewData() is to set chartData, and with the line commented out it's goal would be achieved because of the side effect of the method (which is updating the slider). Not acceptable.
To chartData I added throttle anyways, because fast moving slider will result in many updates and this solves my problem partially(chartData updated only once). But as you may remember the filterChartData() method is costly, and this part will still be running twice.
So the one question is, if my general layout of tackling the problem is OK, or should it be handled way different? At this point I came to conclusion that I'm looking for some way of temporary disabling particular subscription on selectedXRange (without damaging other subscriptions to that variable). Temporary meaning:
(...)
//disable subscription
self.chartInfoController.selectedXRange.accept(newSelectedXRange)
self.chartAreaController.chartData.accept(rawChartData)
//enable subscription
(...)
This seem legit to me, since ChartController as an owner of the subscription and changer of the values may want to disable the subscription whenever it suits him(it?).
Does RxSwift support something like this? If not, then I think I can achieve it myself e.g. via bool property in ChartController, or via adding the subscription to separate disposeBag, which I would dispose and then recreate the subscription. But if it's good thing to do? For example bool solution may be prone to be ill handled when there is some error, and dispose/recreate may be somehow costly, and it may be the case that disposal was not intended to be used like that.
Is there a better practice to handle such situations? As I said I think the problem is common so I hope there is a canonical solution to it :) Thanks for any answer, sorry for the lengthy post.
So the one question is, if my general layout of tackling the problem is OK, or should it be handled way different?
A properly written UI input element observable will only fire when the user makes a change to the UI, not when the program makes a change. For example:
textField.rx.text.orEmpty.subscribe(onNext: { print($0) }) will only print a value when the user types in the textField, not when you call textField.text = "foo" or from a binding .bind(to: textfield.rx.text).
If you wrote the ChartInfoController, I suggest you modify it to work the way the other UI elements do. If you didn't write it, submit an issue to the developer/maintainer.
Does RxSwift support something like [temporarily disabling particular subscription]?
It depends on what you mean by "temporarily disabling". It doesn't support silently unsubscribing and resubscribing but there are plenty of operators that will filter out some events they receive while passing others along. For example filter, throttle, debounce, ignoreElements... There's a lot of them that do that.
Is there a better practice to handle such situations?
Then best solution is mentioned above.
When We have multiple subscriptions to the same Observable, it will re-execute for each subscription.
To stop re-execute for each subscription. RxSwift has several operators for this: share(), replay(), replayAll(), shareReplay(), publish(), and even shareReplayLatestWhileConnected().
read more at (RxSwift: share vs replay vs shareReplay)

How to optimize performance of Results change listeners in Realm (Swift) with a deep hierarchy?

We're using Realm (Swift binding currently in version 3.12.0) from the earliest days in our project. In some early versions before 1.0 Realm provided change listeners for Results without actually giving changeSets.
We used this a lot in order to find out if a specific Results list changed.
Later the guys at Realm exchanged this API with changeSet providing methods. We had to switch and are now mistreating this API just in order to find out if anything in a specific List changed (inserts, deletions, modifications).
Together with RxSwift we wrote our own implementation of Results change listening which looks like this:
public var observable: Observable<Base> {
return Observable.create { observer in
let token = self.base.observe { changes in
if case .update = changes {
observer.onNext(self.base)
}
}
observer.onNext(self.base)
return Disposables.create(with: {
observer.onCompleted()
token.invalidate()
})
}
}
When we now want to have consecutive updates on a list we subscribe like so:
someRealm.objects(SomeObject.self).filter(<some filter>).rx.observable
.subscribe(<subscription code that gets called on every update>)
//dispose code missing
We wrote the extension on RealmCollection so that we can subscribe to List type as well.
The concept is equal to RxRealm's approach.
So now in our App we have a lot of filtered lists/results that we are subscribing to.
When data gets more and more we notice significant performance losses when it comes to seeing a change visually after writing something into the DB.
For example:
Let's say we have a Car Realm Object class with some properties and some 1-to-n and some 1-to-1 relationships. One of the properties is a Bool, namely isDriving.
Now we have a lot of cars stored in the DB and bunch of change listeners with different filters listing to changes of the cars collection (collection observers listening for changeSets in order to find out if the list was changed).
If I take one car of some list and set the property of isDriving from false to true (important: we do writes in the background) ideally the change listener fires fast and I have the nearly immediate correct response to my write on the main thread.
Added with edit on 2019-06-19:
Let's make the scenario still a little more real:
Let's change something down the hierarchy, let's say the tires manufacturer's name. Let's say a Car has a List<Tire>, a Tire has a Manufacturer and a Manufacturer has aname.
Now we're still listing toResultscollection changes with some more or less complex filters applied.
Then we're changing the name of aManufacturer` which is connected to one of the tires which are connected to one of the cars which is in that filtered list.
Can this still be fast?
Obviously when the length of results/lists where change listeners are attached to gets longer Realm's internal change listener takes longer to calculate the differences and fires later.
So after a write we see the changes - in worst case - much later.
In our case this is not acceptable. So we are thinking through different scenarios.
One scenario would be to not use .observe on lists/results anymore and switch to Realm.observe which fires every time anything did change in the realm, which is not ideal, but it is fast because the change calculation process is skipped.
My question is: What can I do to solve this whole dilemma and make our app fast again?
The crucial thing is the threading stuff. We're always writing in the background due to our design. So the writes itself should be very fast, but then that stuff needs to synchronize to the other threads where Realms are open.
In my understanding that happens after the change detection for all Results has run through, is that right?
So when I read on another thread, the data is only fresh after the thread sync, which happens after all notifications were sent out. But I am not sure currently if the sync happens before, that would be more awesome, did not test it by now.

Breeze entity manager "hasChanges" property - how do I find out what is triggering the "true" state?

I have the following event handler in my datacontext:
manager.hasChangesChanged.subscribe(function (eventArgs) {
hasChanges(eventArgs.hasChanges);
});
and in Chrome I've set a break point on the "haschanges(eventArg.haschanges);" line.
The moment I load my app and the process of fetching data begins, this breakpoint is hit. It then proceeds to be repeatedly hit and the "hasChanges" property varies between "true" and "false" many times.
I know from further debug breakpoints that a simple query that "expands" a related table via its navigation property triggers a visit to my "hasChangesChanged" event handler.
What I don't know - as the "eventArgs" is so big and complex - is exactly which of my 5 or so related entities being retrieved is triggering the "true" on the "hasChanges" property. Is there a property within the eventArgs I can inspect to determine which current entity has caused the trip to the hasChangesChanged event handler?
I'm puzzled about why any of what I'm doing is setting "hasChanges" to true as all I do in the first instance is retrieve data. As far as I'm aware, nothing is changed whatsoever at the point the entity manager is convinced that something has changed.
To elaborate, my app prefetches lots of data used for a tree structure at the point where it is sitting waiting for first input from the user. As the user has not had an opportunity of touching anything in the app by this point, why would breeze think that any of the entities concerned have been changed when they've simply been read in from the database?
Use the EntityManager.entityChanged event if you want fine grained information about what has changed. This event gives much more detail but is fired much more often.
http://www.breezejs.com/sites/all/apidocs/classes/EntityManager.html

NSFetchedResultsChangeInsert gets called twice for 1 insert with different object IDs

I'm losing my mind around this question.
So I have a Core Data setup in my iOS app done this way:
http://www.cocoanetics.com/2012/07/multi-context-coredata/
I then insert an object by creating a temporary MOC (as explained in the blog post) and perform saves on all 3 contexts in performBlock: methods.
In a view controller I have an NSFetchedResultsController and it gets notified that I did indeed insert a new object. The problem is that the NSFetchedResultsChangeInsert is fired twice and each time the object that is passed trough has a different objectID (it also is a different object instance in memory). What happens is that I then have 2 rows inserted in my table view but un the SQL database there is only one new. It then of course crashes when I scroll to the bottom of the table view.
If I also perform some updates on the object I get NSFetchedResultsChangeUpdate called only once and with the objectID that was passed in the second NSFetchedResultsChangeInsert call.
The first ID looks like this:
<x-coredata:///ReceivedMessage/t605BB9A7-A04E-4B89-B568-65B12E8C259A2>
The second (and all consequent ones) like this:
<x-coredata://02A917C5-850F-4C67-B8E4-1C5790CF3919/ReceivedMessage/p28>
What could this be? Am I missing out something obvious?
PS: I also checked if the notification comes from the same context, thread, etc. It does.
The two IDs you are seeing may very well represent one object. The difference between them is just that the first one is a temporary object ID, assigned to the object on creation, and the second one is the permanent object ID, assigned to the object when it gets stored to the managed object store (see NSManagedObjectID's isTemporaryID).
To work around this issue you could call NSManagedObjectContext's obtainPermanentIDsForObjects:error: just before you save the temporary MOC. This way the inserted object will have just one ID during the save propagation and the NSFetchedResultsControllerDelegate methods should get called just once.

Update to NSManagedObject causes NSFetchedResultsController delete

I have quite a frustrating problem that I have been struggling with for quite some time.
To provide some context and detail I have an iOS UISplitViewController application - standard master / detail stuff. The master view is a UITableView backed with an NSFetchedResultsController (which loads NSManagedObjects from a SQLite data store).
What seems to be happening is that any update within the details view (which can routinely cause updates to the 'master records' and are flushed to NSManagedObject's and ultimately the SQL data store) causes a DELETE operation on the NSFetchedResultsController.
I assume that this is because the write to the NSManagedObject property(s) are causing a fault of some kind, which in turn causes the NSFetchedResultsController to expunge it from it's cached result set. The end result is that records go 'missing' from the master view (e.g.: UITableCellView's are removed from the master UITableView).
The issue is that I don't want this to happen and I have no idea how to stop it...
Has anyone experienced this issue before and could possibly provide some guidance?
Thanks in advance,
Ben
I'm not sure if this answers your question, but I figured out the solution to a similar problem I was having. I was sorting the objects in my Core Data-backed UITableView by the first letter of the name of each object. In whatever tutorial I read, it told me to put a transient property 'NameInitial' in the NSManagedObject subclass that I would populate with the first letter of the name of that object. I then used that property as my sectionNameKeyPath to sort the objects into the proper sections in my UITableView.
I had a button on each cell that updated a property of the object associated with that cell, and I properly received NSFetchedResultsChangeUpdate messages in my didChangeObject delegate function. HOWEVER, sometimes, cells would get deleted and I would receive the NSFetchedResultsChangeDelete message for no apparent reason.
Then I noticed that the cells that were getting deleted had (null) as the NameInitial property for their associated object. I had forgotten that the transient NameInitial is only stored in memory, and so is not necessarily maintained all the time. Once I manually repopulated the NameInitial property each time I updated a cell, the deleting stopped. So if you are using a transient property to help sort/section your UITableView, this might be your problem.
Hope this helps, and good luck!
-Rick

Resources