Rameswar Prasad
Rameswar Prasad

Reputation: 1331

How to do tight coupling between coredata/NSManagedObject model data changes with apps user interface?

TL;DR - To simplify the whole description, how to go about implementing a non tableview based view controllers UI, when the core data object properties get frequently updated in the background. And with that the visual data representation needs to be updated immediately. [In tableview it's very easy to handle, you use a FRC and just reload the row when an the object updates, through FRCs delegate methods.]


I know about NSFetchedResultsController and mapping through that to an User Interface. But that's only when the User Interface is list based.

In my case multiple hardwares of similar type are connected to my app through bluetooth (BLE), and each normally provides updates with frequency of 1 second generally. E.g Temperature, charge changes.

So here's an example of the UI flow,

List -> Details

Details

  1. Health
  2. Activity
  3. Notification Setting

Details just represents a hardware related data (serial num, firmware version, manufacturing date etc), and it has 3 buttons named above which when tapped pushes to respective controllers.

The Entity model is also designed accordingly, here's a glimpse, I have a the primary entity let's say ABC. Then ABC has one-to-one relationship with other entities like HealthDetails, Activity, NotificationSetting and HardwareDetails. ABC has few attributes like identifier, connected, name, etc.

ListViewController is a UITableViewController and hence there I'm using an NSFetchedResultsController. Now remaining all other view controllers are just normal view controllers, with just Title labels like 'last charged date', which is static and a 'description label' below it which shows the data. Other view controllers are also similarly populated with just buttons, labels etc.

Updating the list view controller is easy with NSFetchedResultsController.

[When a row/cell is tapped by user, I just get the object using indexpath from FRC and inject it to the destination view controller.]

However the other view controllers are not table view based. So I am using notification to control other view controller UI updates.

For e.g when my BluetoothManager receives an update from the hardware regarding a characteristic, I channelise the characteristic id and the data to my DatabaseHelper, which then decodes the data and inserts into the respective managed object, and at this point I just fire a notification with the identifier of the object which got updated.

All the UIViewControllers in the hierarchy which are in the navigation stack are subscribed as observers. And if the identifier in the notification matches the entity object being shown currently then I refresh the UI. Currently all this works fine.

But, I feel it's very clumsy, handling lots of notifications. Is this the only way or are there any better methods existing out there to tightly couple individual core data models to UIs?


I see few comments which suggests to handle all through FRC only, but I'm not sure if FRC can be used in a non tableview based UI representation. I have searched and haven't found much. If anyone knows any tutorial/blog with even theoretical description that would be great help.

Another thing the suggestions on FRC to handle all, I didn't get it completely, do I need to place one FRC per view controller? Isn't there any other way?

Upvotes: 4

Views: 315

Answers (5)

Wain
Wain

Reputation: 119031

So you already have one solution to your issue, using notifications. There's nothing explicitly wrong with that. It could potentially be improved by observing only the ID you're VC is interested in, to prevent multiple VCs from processing and checking if the notification applies to them. This is a serviceable solution.

An FRC is easy to use with a table / grid based layout but that isn't all it's good for, because it offers specific context observation. At its most high level this means responding only to the controllerDidChangeContent: delegate callback to update the UI. This is generally better than your notification because it's specific, though it is a little more costly in terms of memory consumption (this is so minor as to be irrelevant).

If your detail VCs display multiple pieces of information then you may not be using a table / collection view but there is still a list / grid style layout and the FRC index paths could mean something related to that.

The alternate that hasn't been discussed is KVO, which is natively supported by all managed objects. This is the most appropriate solution for you because you are being passed the object and as a result you don't need to do any higher level / context observation. Directly working with KVO is a bit of a pain sometimes so you want to use some helper library, like this one, so you can use blocks to deal with the specifically updated items.

Using KVO gets you data binding, so an update to the underlying model data directly reflects on your UI as a result of the detailed observation callback.

Upvotes: 2

Amin Negm-Awad
Amin Negm-Awad

Reputation: 16660

First of all I understand your concerns about using a to-one pattern for this. (Only for the protocol: It is not a one-to-one pattern, because a delegate can be a delegate of many delegating classes. In reality that happens.)

The to-many pattern is indeed notifications: It is broadcasting. But I do not understand, why you have that many notifications? Simply listen to the context's NSManagedObjectContextDidSaveNotification, which will give you a list of changed objects. It should be easy for the detail view controller to pick up the object it displays.

Did I misunderstand you?

Upvotes: 1

apetrov
apetrov

Reputation: 472

Sorry, I don't have enough reputation to add comment.

I'll join others who suggest to use FRC. I can't help you with blog/tutorial, but CoreStore ObjectMonitor is a very good example for that it can be used in different (non tableview) context. I'm sure it'll not be a problem for you to get it.

Upvotes: 2

thephatp
thephatp

Reputation: 1489

You mentioned changing "ViewControllers," so I'm assuming when you push as you said in the comments "List > Health > Activity > Settings" that each of these are UIViewController classes, right? If so, implement the viewWillAppear method in each of these, and be sure to do all of your UI initialization there. By doing so, you guarantee that when the pop happens, this method will be called and your UI will be updated (b/c you have the logic now in this method to do the update). I used to do my UI initialization in viewDidLoad but had the same problem you are experiencing now. Moving it to viewWillAppear was the solution in my situation.

After this, if you still aren't seeing the right values in the UI, put breakpoints in the viewWillAppear method and inspect the data that's in your model. Perhaps the model is incorrect--in which case, perhaps you do a query (search) to pull what you need again. This shouldn't be a problem unless you are using parent/child contexts and are not committing the child context changes up to the parent.

But I feel it's very clumsy, handling lot of notifications. Is the only way or any better methods exists out there to tightly couple individual core data models to UIs.

Honestly, I don't think there is a better model, since you aren't using FRC on the other views. And if you aren't using table views to reload or get new data, then you definitely don't need to use FRC (though I won't go so far to say you shouldn't because I can't firmly back that up).

I use this exact model in my application, which has several different views which receive the notification via observers--same as how you are doing. If this isn't working well, there's one other option to consider.

Upvotes: 2

Gargoyle
Gargoyle

Reputation: 10324

You shouldn't lose anything. The delegate on the NSFetchedResultsController will tell you any time something is updated. If you have a visible UI, then your UI will update. If you don't have a visible UI, or don't want one, you can still link that FRC to your specific NSManagedObject subclass for the health details and then take whatever action is appropriate based on the fact that something changed.

Depending on what you're doing, it may be as simple as implementing controllerDidChangeContent(_:)

If that doesn't answer your concern, you're going to have to provide more details as to what the specific issue is.

Upvotes: 2

Related Questions