Delete/Insert versus Update for NSManagedObjects when data should be updated - ios

I'm trying to solve problem which appears during this case:
User can initiate data loading from external source, when data is loaded, it is saved via CoreData. Then it is displayed in some views and some other classes got references to NSManagedObjects.
Data loading can be initiated by other condition (for example, when application resumes from background). New external data is received, dataController deletes previous and creates new data. And here is the problem.
I want to notify all data consumers-classes that they should load new instances (send them references to deleted objects, so they can compare references with ones they own and determine should they ask for new data version or not). But after deletion consumer-class has reference to fault without properties, its ObjectID is useless (because new instance was saved) and I don't know how load its new version.
I can implement some NSManagedObject wrapper:
#interface Model : NSObject
- (id)initWithUniqueId:(id)uniqueId dataObject:(NSManagedObject *)dataObject;
#property (nonatomic, strong, readonly) id uniqueId;
#property (nonatomic, strong, readonly) NSManagedObject *dataObject;
#end
This object can reload itself after dataObject becomes fault. But maybe this approach is wrong and this overhead is not needed? And NSManagedObject should be deleted only if it is really deleted, not updated? And then if object is updated, we can use KVO to handle properties changes, and if object is deleted, we can observe NSManagedObjectContext notifications for changes and look for deleted objects.
I just want to know which way would you prefer and why (maybe you like some other approach)? Thanks in advance.

If you are using an external data source, your own version of some kind of unique ID makes sense.
Then everything becomes simple. You load the data, update the persistent store when you save the context, and send of a notification via the NSNotificationCenter. All listeners can now simply update their data by re-fetching.
(Fetched results controllers that implement the delegate methods do not even have to be notified via the notification center.)

Related

What is the best practice to pass object references between two queues with CoreData?

I am facing a decision to be made for an applications architecture design.
The Application uses CoreData to persist user information, the same information is also stored on a remote server accessible by a REST-Interface. When the Application starts I provide the cached information from CoreData to be displayed, while I fetch updates from the server. The fetched information is persisted automatically as well.
All of these tasks are performed in background queues as to not block the main thread. I am keeping a strong reference to my persistenContainer and my NSManagedObject called User.
#property (nonatomic, retain, readwrite) User *fetchedLoggedInUser;
As I said the User is populated performing a fetch request via
[_coreDataManager.persistentContainer performBackgroundTask:^(NSManagedObjectContext * _Nonnull context) {
(...)
NSArray <User*>*fetchedUsers = [context executeFetchRequest:fetchLocalUserRequest error:&fetchError];
(...)
self.fetchedLoggedInUser = fetchedUsers.firstObject;
//load updates from server
Api.update(){
//update the self.fetchedLoggedInUser properties with data from the server
(...)
//persist the updated data
if (context.hasChanges) {
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
NSError *saveError = nil;
BOOL saveSucceeded = [context save:&saveError];
if (saveSucceeded) {
//notify the App about the updates
//**here is the problem**
}
};
}];
So the obvious thing about this is, that after performing the backgroundTask, my self.fetchedLoggedInUser is not in memory anymore, because of its weak reference to the NSManagedObjectContext provided by the performBackgroundTask() of my PersistentContainer.
Therefore, if I try to access the information from another Model, the values are nil.
What would be the best practice to keep the fetched ManagedObject in Memory and not have to fetch it again, every time I want to access its values?
A) In the Documentation, Apple suggests using the objectID of an ManagedObject, to pass objects between queues
Passing References Between Queues
NSManagedObject instances are not intended to be passed between
queues. Doing so can result in corruption of the data and termination
of the application. When it is necessary to hand off a managed object
reference from one queue to another, it must be done through
NSManagedObjectID instances.
You retrieve the managed object ID of a managed object by calling the
objectID method on the NSManagedObject instance.
The perfectly working code for that situation would be to replace the if(saveSucceeded) Check with this Code:
if (saveSucceeded) {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error = nil;
self.fetchedLoggedInUser = [self.coreDataManager.persistentContainer.viewContext existingObjectWithID:_fetchedLoggedInUser.objectID error:&error];
(...)
//notify the App about the updates
});
}
But I think this may not be the best solution here, as this needs to access the mainContext (in this case the persistentContainer's viewContext) on the mainQueue. This is likely contradictory to what I am trying to do here (performing on the background, to achieve best performance).
My other options (well, these, that I came up with) would be
B) to either store the user information in a Singleton and update it every time the information is fetched from and saved to CoreData. In this scenario I wouldn't need to worry about keeping the NSManagedObject context alive. I could perform any updates on a private background context provided by my persistentContainer's performBackgroundTask and whenever I'd need to persist new / edited user information I could refetch the NSManagedObject from the database, set the properties, save my context and then update the Singleton. I don't know if this is elegant though.
C) edit the getter Method of my self.fetchedLoggedInUser to contain a fetch request and fetch the needed information (this is probably the worst, because of the overhead when accessing the database) and I am not even sure if this would work at all.
I hope that one of these solutions is actually best practice, but I'd like to hear your suggestions why/how or why/how not to handle the passing of the information.
TL:DR; Whats the best practice to keep user information available throughout the whole app, when loading and storing new information is mostly done from backgroundQueues?
PS: I don't want to fetch the information every time I need to access it in one of my ViewControllers, I want to store the data on a central knot, so that it is accessible from every ViewController with ease. Currently the self.fetchedLoggedInUser is a property of a singleton used throughout the application. I find that this saves a lot of redundant code, using the Singleton makes loading and storing the information clearer and reduces the access count to the database. If this is considered bad practice I'd be happy to discuss about that with you.
Use a NSFetchedResultsController - they are very efficient and you can use them even for one object. A FetchedResultsController does a fetch once and then monitors core data for changes. When it changes you have a callback that it has changed. It also works perfectly for ANY core-data setup. So long as the changes are propagated to the main context (either with newBackgroundContext or performBackgroundTask or child contexts or whatever) the fetchedResultsController will update. So you are free to change your core-data stack without changes your monitoring code.
In general I don't like keeping pointers to ManagedObjects. If the entry is deleted from database then the managedObject will crash when you try to access it. A fetchedResultsController is always safe to read fetchedObjects as it tracks deletions for you.
Obviously attach the NSFetchedResultsController to the viewContext and only read it from the main thread.
I came up with a very elegant solution in my opinion.
From the beginning I was using a Singleton called sharedCoreDataManager, I added a property backgroundContext that is initialized like so
self.backgroundContext = _persistentContainer.newBackgroundContext;
_backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_backgroundContext.retainsRegisteredObjects = YES;
and is retained by sharedCoreDataManager. I am using this context to perform any tasks. Through calling _backgroundContext.retainsRegisteredObjects my NSManagedObject is retained by the backgroundContext, which is itself (like I said) retained by my Singleton sharedCoreDataManager.
I think this is an elegant solution as I can access the ManagedObject threadsafe from the background anytime. I also won't need any extra class that holds the user information on top. And on top of that I can easily edit the user information at anytime and then call save() on my backgroundContext if needed.
Maybe I am going to add it as a child to my viewContext in the future, I'll evaluate the performance and eventually update this answer.
You are still welcome to propose a better solution or discuss this topic.

Core Data Installation

I am using core data to save data from web services in my app. On the first time of running, app creates the core data instance and attributes and properties and save all the data. My question is that, when the application runs second time or many times, Is the core data creates its instance and properties and save all data again, or again and again? I am sorry if my question is not relevant.
Thanks
If the Webservice + code for storing data to coredata gets called every time you run app, core data will store objects again and again
You can solve this by 2 ways
If your data from server remains the same, you can set a flag and check that coredata insertion should execute only once
If you data from server may keep on changing, you can Update data instead of inserting it again to avoid duplicate records.
If u want to make sure the data is unique, u should make sure the data object should have a unique key.
For example:
#interface Person : NSManagedObject
#property (nonatomic, retain) NSNumber * pid;
#property (nonatomic, retain) NSString * name;
#end
The property of Person.pid should be unique. It means that u can only get one person max with the appointed pid.
So before u insert new object, u should query the db with NSFetchRequest(NSPredicate) like this:
NSNumber *aimPid;// the person's pid
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"pid == %#", aimPid]];
If person exist, then just update and save.
If person not exist, then insert a new one and save.
More:https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/

CoreData: Using transient properties with read-only persistent store

Dealing with managed objects (NSManagedObject) from read-only persistent store I was trying to use transient properties to store some temporary values. Took in account transient properties were not saved to persistent store I supposed there was nothing wrong to use them for cache purposes. But as it turned out you cannot write data even in transient properties of managed objects from read-only store. During NSManagedContext's save operation i got this error
"Cannot update objects into a read only store"
(I'm sure only transient properties were changed.)
Why is that? Can it be considered as a NSManagedObjectContext's bug? Thanks for sharing your ideas.
This is expected behavior. A read only store cannot be modified. Not even in the managed object context (in memory). That is the meaning of "read only". Hardly a bug.
The solution is fairly simple. Create a second in-memory persistent store and integrate it into your managed object model via Configurations. Keep track of your transient property via this store. Perhaps you have to create a "wrapper" entity and link it to the read-only store via a relationship.
Despite the effort of creating a more complicated model setup, I think this is a feasible solution because once you have done this setup you can basically forget about it.
You can declare your own property in entity class:
#interface DBExample : NSManagedObject
#property (nonatomic, strong) NSDictionary *userInfo;
#end
Implementation:
#implementation DBExample
#synthesize userInfo = _userInfo;
#end
And by the way, why are you saving context with attached read-only persistent store?

RestKit serialization creating duplicate entries

When saving to my database with Restkit, I get duplicate entries.
I'm not sure how to prevent this. The intended behavior is that if the object already exists, then it should update that existing object with the columns that happen to be different.
I set a key identifier here:
[mapping setIdentificationAttributes:#[MYObjectAttributes.userID]];
but I suppose there is something else I am supposed to do. I've seen other questions more related to core-data that manually do a fetch request looking for an existing entry, before writing it, this seems expensive and restkit is supposed to have a solution for this already.
RestKit is for mapping a RESTful service to core data. If you are not using the RKObjectManager for updating (that is, you want to put something on your REST service) and only want to do a local change you should get the managed object and work with it outside the context of RestKit.
If you need to check whether a managed object exists locally or not, you should do it with a Managed Object Context rather than try to use RestKit for it.
Along with attributes, you can also detect, whether managed object is new or not. RestKit has created a category over NSManagedObject, where it has provided 1 function:
/**
* Returns YES when an object has not been saved to the managed object context yet
*/
#property (nonatomic, readonly) BOOL isNew;
https://github.com/RestKit/RestKit/blob/fc101de9133d96bc0e2221153de7f699f8c1f06d/Code/CoreData/NSManagedObject%2BRKAdditions.m

iOS Core Data Saving MOC issue due to Validation rules context

I'm using Magical Record with the Core Data framework, and I've run into issues saving deleted objects from my MOC. I have a Patient NSManagedObject that has a set of Notes NSManagedObjects, so MOs look like like so:
Patient.h
#interface Patient : NSManagedObject
#property (nonatomic, retain) NSSet *notes;
#end
#interface Patient (CoreDataGeneratedAccessors)
- (void)addNotes:(NSSet *)values;
- (void)removeNotes:(NSSet *)values;
#end
Notes.h
#interface Note : NSManagedObject
#property (nonatomic, retain) NSDate * creationDate;
#property (nonatomic, retain) NSString * noteText;
#property (nonatomic, retain) Patient *patient;
#end
I also have validation rules to make sure the noteText property is not null or empty. Now in my view controller in the viewDidLoad method I'm creating a new note managed object using:
Note* lNote = [Note MR_createInContext:localContext];
So, the note is created instantly once the view loads, ready for the noteText property to be modified via a UITextView. If the user does not enter any text and presses Save the validation triggers and prevents the save, which is all good.
The problem occurs when I click on my notes archive folder button which is in this same view controller, once pressed, it presents a modal view controller and lets the user either load or delete notes, since I'm trying to delete a note from this archive screen, I have to rollback the previous note I created in the viewDidLoad method, so that I can delete the notes and save the default context, otherwise when I'm trying to save the deleted objects the validation rule for noteText property kicks in from the MOC.
I notice that this is more of a logical or work flow type of problem, but I want to prevent the rollback of the note created in the defaultContext and still be able to save the defaultContext with the deleted notes.
I've tried using different MOC but that presented more issues, one MOC for retrieving the patients and another one for creating notes.
Creating a different managed object context is the correct solution for your problem. A MOC is a "scratchpad" and you need two scratchpads in the scenario you describe. You are essentially interrupting the note creation process with another note editing process.
That being said, you could just delete the empty note and recreate it when the other controller is dismissed. You could also set the note text to #"". There are all kinds of hacky ways to accomplish this, but using two MOCs is the cleanest method.

Resources