Core Data NSManagedObject - tracking if attribute was changed - objective-c

I have an object - Config. I want to know if the Account attribute on Config has changed. When this happens, I want to send a NSNotification so that all code that cares about when the Account changes will know. My initial thought was in my NSManagedObject subclass that I would override the setAccount method to set a transient attribute AccountDidChange to true. Then in didSave if AccountDidChange was true I would send the notification and then set it back to false. Is there a better way? My issue though is that from what I've read, by changing AccountDidChange back to false, I would have dirtied my object and need to save again.
A little more info:
The Config object is the current configuration of the application. Account could actually be changed to ActiveAccount. There is a relationship to the Account Entity that has a list of all Accounts. The idea is that the user can change the active account of the application. So we have a set of servers and the user can only be logged into one at a time. Config.Account points to that active account and it is used to setup connections to the server to retrieve information. I am using this notification that Config.Account has changed to tell other objects to clean up their information - like list of alerts. Basically, all information is per Account so it needs to be removed and then refetched on its next load with the new active account.
Also, the given names are not my actual object names - just trying to make the example easier to follow.

Take a look at KVO (Key-Value Observing): Key-Value Observing Programming Guide. That's the standard way to do this in Cocoa, and is a fundamental technology that you need to understand to be a good Cocoa programmer.
KVO will let objects that care about changes to the Account property (which you should probably name account, not Account) register to be notified when the property is changed. KVO will "just work" for standard NSManagedObjects, without any additional work on your part.
The relevant methods are as follows:
-addObserver:forKeyPath:options:context: which you call on your Config object to set up the observation
-observeValueForKeyPath:ofObject:change:context: which will be called on the observer object anytime an observed value is changed
-removeObserver:forKeyPath: which you need to make sure you call when the observer no longer needs change notifications (including before the observer is deallocated).
This is all described in a lot more detail in the linked documentation.
EDIT BELOW:
Without knowing anything about your application, it's hard to know why you'd want to be notified only upon save. NSManagedObjectContext posts NSManagedObjectContextWillSaveNotification and NSManagedObjectContextDidSaveNotification. The notification's userInfo has arrays containing inserted, updated and deleted objects, but the notifications aren't as fine-grained as individual properties. I suppose you could manually keep track of changed accounts between didSave notifications. That'll probably get inefficient if you have lots of Configs in your store.
Changes to NSManagedObjects are immediate, they're just not saved to the persistent store until you call save: on the managed object context. Perhaps if you explain more about exactly what you're trying to accomplish and why, I can better help.

Related

Use RESTKit for two way synchronization

is it possible to use RESTKit for two way synchronization?
I played aroud with RESTKit and CoreDate. Now I can download all data from my REST service and all changes (create/modify/delete objects) in CoreDate will be overwritten by RESTKit.
Now I want to choose between both versions (the local version or the remote version). How do I do this? Is there a way to manipulat the mapping, or something like that?
update:
My app should synchronize after some changes or after a specific delay (not difficult). Every object has a change date. Until now I want to keep the newer one (if they are equal the local one).
I hope RestKit is made for changing the strategy how it merges objects. something like a block I can set, where I get both objects and can return the merged object.
What I got so far:
I load the object via RestKit but do not persist them. Also I setup a CoreData store where I store the local objects. After loading the remote object I start to synchronize my self. First searching for pairs and then decide which to take, delete, create, overwrite, and so on...
But this is a big bunch of work and I think RestKit is doing something similar. Why not simply changing the strategy of RestKit for my requirements.
Well this would be the "syncing down" thing. After this I could write the synchronized data set back to the service. The changes are not very frequently so I will not have to check for new changes.
I hope now it's a little bit clearer
What you really want to do is validate the data coming in.
Since RestKit is using CoreData it automatically uses the validation built into CoreData (see here)
Here is an example that will ensure that the date never gets changed to an earlier one.
- (BOOL) validateChangeDate:(id *)ioValue error: (NSError **)outError {
if ([[*ioValue laterDate:self.changeDate] compare:self.changeDate] == NSOrderedSame)
*ioValue = self.changeDate;
return YES;
}
Note: There may be better/faster ways to test to see if we need to change the date
Note 2: According to the docs, if RestKit sees a value rejected, it fails the entire object, which we don't want, so that's why we always return YES and just change the incoming date
Note 3: We also don't want to change the value unless we have to because of what the CoreData docs say here
You may be able to leverage KVC validation to achieve this as it allows you to check and then edit or deny the change for each object and key. Check these docs.

Is it good practice to work directly with core data whenever I need to manipulate my business objects?

For example. if I have a chat app that uses objects like: ChatRoom and ChatMessage. (both subclasses of NSManagedObject)
And throughout the app I need to: search chat rooms / add massages / create chat rooms and messages / and any other manipulate.
Is it ok if I do everything directly with core data? I mean every time I need to search a chat room or something like that, to do it with NSFetchRequest or NSFetchedResultsController.
You can access the data whenever you need, of course. On the other hand you should try to use caching mechanism as much as possible.
For example, if you are using your data in UITableView you should defintely go with NSFetchedResultsController as it was created explicitly for UITableViews. From the apple docs on NSFetchedResultsController:
It optionally monitors changes to objects in its associated managed object context, and reports changes in the results set to its delegate (see “The Controller’s Delegate”).
It optionally caches the results of its computation so that if the same data is subsequently re-displayed, the work does not have to be repeated (see “The Cache”).
Otherwise, if you need the data just temporarily, you can access them of course using NSFetchRequest each time they are needed or cache them in your business objects if they don't change or you know their lifetime otherwise.

NSManagedObject changed properties after save

Is it possible to find out which properties were saved on a managed object after the save occurs? For example, I have someone listening for managed object context saves, (NSManagedObjectContextDidSaveNotification) and I want to know which properties on the objects were saved.
The NSManagedObjectContextDidSaveNotification does contain all three bits of information you would need to sync with a server. Check the [notification userInfo] and you will find three sets inside: NSInsertedObjectsKey, NSUpdatedObjectsKey, and NSDeletedObjectsKey
If you want to know what properties on an entity have changed that would require that you track them yourself using KVO. I would recommend against this as the odds of that level of effort being worth it over just pushing the entire object up to a server are slim.
Update #2
On further poking around:
From the NSManagedObjectContextWillSaveNotification you could loop through each set and reference the changedValues method. You could keep a reference to that dictionary until after you receive the NSManagedObjectContextDidSaveNotification and then process the changes. Still sounds very heavy to me.
Update
What is your end goal?!?!
If you are trying to figure out what to push to a server then being at the attribute level is too low. You should be syncing at the entity level.
If you are just trying to keep some internal consistency inside of your application then you are thinking way, way too low level. This is a solved problem. Core Data solved it.
Why don't you get them when they are about to be saved. Subscribe to NSManagedObjectContextWillSaveNotification and check insertedObjects, updatedObjects and deletedObjects of the NSManagedObjectContext.
Update:
Even easier, get the user info of the NSManagedObjectContextDidSaveNotification
From the documentation:
Typically, on thread A you register for the managed object context
save notification, NSManagedObjectContextDidSaveNotification. When you
receive the notification, its user info dictionary contains arrays
with the managed objects that were inserted, deleted, and updated on
thread B.
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/coredata/Articles/cdConcurrency.html#//apple_ref/doc/uid/TP40003385-SW1
Here's the solution I settled with. I have one singleton class that is notified when a context saves. The NSManagedObjectContextWillSave notification tells me which things have changed so I store them in a dictionary with the key being the context that saved. Then when I get the NSManagedObjectContextDidSave notification, I check the dictionary for the associated context. Finally, I remove that entry from the dictionary. Does that seem reasonable?

Restore one RCP view while restoring another

Two views in my application need to load same information when restoring state. My idea was, to avoid saving it twice, to have one view create another in init orcreatePartControl if it wasn't created yet. However,
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().showView(...)
doesn't work there, as getActivePage() returns null. Is it possible to work around this?
Delegate to a manager or service to load/maintain/save the shared state. That will ensure the first access initializes your information. When the view is instantiated just go to the manager and retrieve the information. If the user never instantiates your view, then you never had to do the extra work.
In the general case, you can't create/instantiate one view while creating/activating another view. Eclipse won't allow it, and will generate ERRORs in the error log.
EDIT:
3 standard persistence patterns I've seen used (and/or misused :-) are:
1) Have your plugin get its state location and simply serialize you state out there. (location provided for free if you subclass org.eclipse.core.runtime.Plugin) You can do it in your activator stop(BundleContext) method. You can uses classes like org.eclipse.ui.XMLMemento to serialize to/from XML if you don't already have a solution.
2) if you subclass org.eclipse.ui.plugin.AbstractUIPlugin you can use org.eclipse.ui.plugin.AbstractUIPlugin.getDialogSettings() to store your state. Potentially a little bulky as you would have to keep it up to date.
3) have your common manager update a preference, potentially using another serialization technique.

How to set modification date every time NSManagedObject changes?

Every time one of my managed objects is modified, I want to store the date it was modified (in an attribute which is already in my data model), for convenience when I'm syncing with other clients.
Is there a way to do this without A) re-writing all my setters manually or B) relying on myself to always change the modification date every single time the object is modified?
You could register with the notification center to observe the NSManagedObjectContextObjectsDidChange notification.
This will tell you what objects are changed. You can check to see if your object is among them and take action accordingly.
Just for completeness, you can also do this in -willSave if you already have a subclass of NSManagedObject.