I am using RestKit with Coredata and fetching data from the server and displaying.
Now I am doing a post from the client and this object gets updated as part of the response that comes back from the server. This is where the problems starts.
I looked for the correct way to implement this and came across 2 main points.
MOCs should not be shared across threads
An object created in the MOC is not available in another thread without saving.
But i think since the record gets updated from server response, its no longer finding the orig object. I just dont knw what the right fix is.
Here is my code
1. Create local entity
NSEntityDescription *itemEntity = [NSEntityDescription entityForName:ENTITY_ITEM inManagedObjectContext:self.managedObjectContext];
Item *item = [[Item alloc]initWithEntity:itemEntity insertIntoManagedObjectContext:self.managedObjectContext];
// Set params on item here
// Then save it
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
DBGLog(#"Tried to save the new item but failed - error %#, %#", error, [error userInfo]);
}
// Then I make the RestKit call to post the item
// The server updates the item Id
[SharedManager postItem:item success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
// successful case
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
// failure case
}];
It looks like when its trying to make the response it doesnt find the object.
And i get this exception -
*** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x156c87b0 <x-coredata://A42ABF18-01B6-4D78-B81B-62D8B604EB52/Item/p6>''
*** First throw call stack:
(0x2f853f0b 0x39feace7 0x2f5b7fd1 0x2f61a655 0x2f6246a7 0x2f6326e5 0x2f632a95 0x2f63356f 0x3a4d3d3f 0x3a4d86c3 0x2f628e7b 0x2f633271 0x2f5c7f49 0x1c67fb 0x2f62b9cd 0x3a4d9b3b 0x3a4d3d3f 0x3a4d66c3 0x2f81e681 0x2f81cf4d 0x2f787769 0x2f78754b 0x346f46d3 0x320e6891 0x72561 0x3a4e8ab7)
libc++abi.dylib: terminating with uncaught exception of type _NSCoreDataException
If I dont do a "save" then I see Cocoa Error 133000 on 4S devices. So there is definitely something I am messing up.
Appreciate any insights!
Your comment is along the correct lines, but not the correct solution. The problem is that you only save the main thread context and the change doesn't get pushed up to the parent (the persistent context). So, instead of calling if (![self.managedObjectContext save:&error]) { you should be calling if (![self.managedObjectContext saveToPersistentStore:&error]) {
Related
I've been testing the new core data stack in iOS 10. My test app parses JSON data into core data and I am trying to make this happen while the user has access to the UI.
I am using the default core data stack and using a background context.
In AppDelegate.m:
- (NSPersistentContainer *)persistentContainer {
// The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
#synchronized (self) {
if (_persistentContainer == nil) {
_persistentContainer = [[NSPersistentContainer alloc] initWithName:#"CoreDataTestingMDC"];
[_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
if (error != nil) {
NSLog(#"Unresolved error %#, %#", error, error.userInfo);
abort();
}
}];
}
}
_persistentContainer.viewContext.automaticallyMergesChangesFromParent = YES;
return _persistentContainer;
}
I have a simple master-detail UI that shows the core data entities in the master view controller and detailed attributes in the detail view. If the user does not scroll the master view, everything works fine. If the user scrolls, I usually get this error on save:
Unresolved error Error Domain=NSCocoaErrorDomain Code=133020 "(null)" UserInfo={conflictList=(
"NSMergeConflict (0x600000667c00) for NSManagedObject (0x610000096490) with objectID '0xd000000000440000 <x-coredata://CFF27A51-8F9E-4898-A4EA-CD85C0AFF300/ContentItem/p17>'
with oldVersion = 44 and newVersion = 45...
It goes on to list the conflicting items which have exactly the same properties.
Also in my AppDelegate, I added a simple convenience method to generate the background context:
- (NSManagedObjectContext *)createBackgroundContext {
return [self.persistentContainer newBackgroundContext];
}
This is passed back to the AppDelegate for a save operation:
- (void)saveContext:(NSManagedObjectContext *) theContext {
NSError *error = nil;
if ([theContext hasChanges] && ![theContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, error.userInfo);
abort();
}
}
The UI is running on the viewContext as expected. I have been very careful to use the background context for all JSON parser writing. No idea why this is crashing.
Update:
It appears that the error occurs whenever the app is run after an initial run. I can test it fine on a clean simulator or after deleting the app. It parses data into core data fine and will also update while the user is interacting with the app. On a second build and run, the app will crash with the above error.
It looks to me that you are having multiple writes taking place concurrently. To solve this you need write to core data in a single synchronous way.
In your core-data manager create a NSOperationQueue
_persistentContainerQueue = [[NSOperationQueue alloc] init];
_persistentContainerQueue.maxConcurrentOperationCount = 1;
And do all writing using this queue:
- (void)enqueueCoreDataBlock:(void (^)(NSManagedObjectContext* context))block{
void (^blockCopy)(NSManagedObjectContext*) = [block copy];
[self.persistentContainerQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{
NSManagedObjectContext* context = self.persistentContainer.newBackgroundContext;
[context performBlockAndWait:^{
blockCopy(context);
[context save:NULL]; //Don't just pass NULL here. look at the error and log it to your analytics service
}];
}]];
}
When you call enqueueCoreDataBlock the block is enqueued to ensures that there are no merge conflicts. But if you write to the viewContext that would defeat this setup. Likewise you should treat any other contexts that you create (with newBackgroundContext or with performBackgroundTask) as readonly because they will also be outside of the writing queue.
At first I thought that NSPersistentContainer's performBackgroundTask had an internal queue, and initial testing supported that. After more testing I saw that it could also lead to merge conflicts.
Also facing the same issue. But i solved it using the MergePolicy of ManagedObjectContext. By default the merge policy is NSMERGEPOLICYERROR. By changing it to NSMergeByPropertyObjectTrumpMergePolicy fixes the NSManagedObject Conflict issues for me.
Check which merge policy suites your requirement.
I have a relatively simple entity. When I create it, set its attributes, and save it, it saves successfully. I can later retrieve it and it is not nil and I get a successful save message from MagicalRecord.
When I retrieve it and try to access any attribute though the attribute is nil. The entity itself is fine but the attributes are all nil. I have checked they are all definitely set correctly before I save.
I haven't encountered this problem before. Why could it be occurring?
NB: This doesn't happen every time. Most times I call the method to create and save this entity it can later be retrieved without any issues. The problem is intermittent but possible to replicate on every run.
Code:
Entity1 *entity1 = [Entity1 MR_createEntityInContext:localContext];
[entity1 setUpEntity:myobject];
EntityChild *entityChild=[EntityChild MR_createEntityInContext:localContext];
[entityChild setUpEntityChild:entity.child withContext:localContext];
[entityChild setEntity1:entity1];
[localContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
}];
Update:
If I look in the sqlite database and search for the entity it actually doesn't exist at all. So MagicalRecord tells me it saves, CoreData lets me retrieve a non-nil object (albeit with nil attributes) but no record exists in the database.
I did not understand ur code standards. As I am new to IOS Development. I Used below code for retrieving.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entityRef = [NSEntityDescription entityForName:#"Entity1" inManagedObjectContext:appDelegate.managedObjectContext];//localContext
[fetchRequest setEntity:entityRef];
NSError *error=nil;
NSArray *detailsArray = [appDelegate.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error) {
NSLog(#"Unable to execute fetch request.");
NSLog(#"%#, %#", error, error.localizedDescription);
}
Saving the data
NSManagedObjectContext *context = [appDelegate managedObjectContext];//localContext
NSManagedObject *objectRef = [NSEntityDescription
insertNewObjectForEntityForName:#"Entity1" inManagedObjectContext:context];
[objectRef setValue:#"IOS" forKey:#"Name"];
[objectRef setValue:#"positive" forKey:#"Attitude"];
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
Hope it helps you...!
Ok, I got to the bottom of this. It wasn't a problem with the code when I did the save. It was actually a problem with some code in another class that was retrieving the data from the wrong context. When I changed the context it worked correctly.
I'm still not sure why this only happened occasionally and not every time the code was run but it's working now.
Thanks for your help anyway everyone.
I'm following the Apple's iCloud Programming Guide for Core Data, the Using the SQLite Store with iCloud section, and you are told there to listen for the NSPersistentStoreCoordinatorStoresWillChangeNotification iCloud event this way:
[[NSNotificationCenter defaultCenter] addObserverForName:NSPersistentStoreCoordinatorStoresWillChangeNotification
object:self.managedObjectContext.persistentStoreCoordinator
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
[self.managedObjectContext performBlock:^{
// disable user interface with setEnabled: or an overlay
if ([self.managedObjectContext hasChanges]) {
NSError *saveError;
if (![self.managedObjectContext save:&saveError]) {
NSLog(#"Save error: %#", saveError);
}
}
else {
// drop any managed object references
[self.managedObjectContext reset];
}
}];
}];
I've put such code within my AppDelegate's application:didFinishLaunchingWithOptions: method and, when the notification is received, I get this exception:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Can only use -performBlock: on an NSManagedObjectContext that was created with a queue.'
According to the document, this should work... what could am I doing wrong?
Thanks
The error:
Can only use -performBlock: on an NSManagedObjectContext that was created with a queue.
...is quite clear. You cannot call performBlock: on a managed object context unless that context was created using one of the queue-type concurrency options. Meaning that when you created the context, you must have used initWithConcurrencyType: and used either NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType as the argument.
This has nothing at all to do with the notification, only with the fact that you're calling performBlock: when you didn't create the context correctly.
I'm getting no where with an issue that seems too stupid to exist. I have a UITableView that uses CoreData to generate objects.
I get an error returned here at [context save]:
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
Stop *stop=[self.fetchedResultsController objectAtIndexPath:indexPath];
[context deleteObject:stop];
NSError *error = nil;
if (![context save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
When I pass the stop object and a context (returned by a fresh call to [self.fetchedResultsController managedObjectContext]; another view and save it like so:
Stop *object= [[self fetchedResultsController] objectAtIndexPath:indexPath];
[[segue destinationViewController] setStop:object];
[[segue destinationViewController] setContext:[self.fetchedResultsController managedObjectContext]];
-(void)viewWillDisappear:(BOOL)animated
{
NSError *error = nil;
self.stop.locationText=self.textLocationField.text;
[self.stop.managedObjectContext save:&error];
if (![self.context save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
The error is an NSMergeConflict which I understand occurs when multiple MOCs are acting on the same data. But I must be missing something fundamental because I don't believe I have two Contexts. Aren't I simply creating, modifying, and deleting the the object in a single context?
After a lot of trial and error turns out that you get an NSMergeConflict when you do not have by directional relationships. So if you have a person that has Feet your feet MUST point back to a person in the relationship graph.
Note that you do not need to pass the context around. You can access the property managedObjectContext that each NSManagedObject subclass provides. Thus
Stop *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSManagedObjectContext *context = object.managedObjectContext;
[context deleteObject:object];
// make sure you delete the table view cell as well
[context save:&error];
and for updating
self.stop.locationText = newText;
[self.stop.managedObjectContext save:&error];
The call to updatedObjects is not necessary. What did you want to accomplish with this?
I'd like to delete a core data object by fetched the object first, so
in FetchObject.m
- (void) actionDelete {
AModel *aModel = [[aModel alloc] init];
AObj *aObj = [aModel readDataWithAttributeName:#"keyword" attributeValue:#"value"];
[aModel deleteObject:aObj];
}
aObj did fetch and obtain.
in AModel.m
- (void)deleteObject:(AObj *)aObj
{
[appDelegate.managedObjectContext delete:aObj];
NSError *error;
if (![appDelegate.managedObjectContext save:&error]) {
NSLog(#"Error: %#", [error description]);
}
}
But, when I test it, here came out an error
-[NSManagedObjectContext delete:]: unrecognized selector sent to instance 0xa43ece0
After searching the solution a bit, seems like the target has been release before deleteObject.
Is there any way to solve the problem?
The following code is causing the issue:
[appDelegate.managedObjectContext delete:aObj];
Replace it with:
[appDelegate.managedObjectContext deleteObject:aObj];
NSManagedObjectContext doesn't have a delete method, it only has a deleteObject method.
- (void)deleteObject:(NSManagedObject *)object
Parameters
object
A managed object.
Discussion
When changes are committed, object will be removed from the uniquing
tables. If object has not yet been saved to a persistent store, it is
simply removed from the receiver.