managed object property becomes nil after merging managed object contexts - ios

I have a managed object named SpecialItem and call setSubcategory to change the subcategory. When I save the temporary context and merge with the main context, somehow setSubcategory is called passing in nil as the subcategory. This often results in the SpecialItem object being saved with myProperty set to nil. I don't know what is calling setSubcategory. I'm not explicitly calling setSubcategory:nil.
My question is, what is happening and how can I fix this?
This is the managed object implementation:
#interface SpecialItem : GenericItem
#property (nonatomic, strong) Subcategory *subcategory;
#property (nonatomic, strong) MyProperty *myProperty;
#end
#implementation SpecialItem
#dynamic subcategory;
#dynamic myProperty;
- (void)setSubcategory:(Subcategory *)subcategory
{
[self willChangeValueForKey:#"subcategory"];
[self willChangeValueForKey:#"myProperty"];
[self setPrimitiveValue:subcategory forKey:#"subcategory"];
[self setPrimitiveValue:subcategory.category.myProperty forKey:#"myProperty"];
[self didChangeValueForKey:#"myProperty"];
[self didChangeValueForKey:#"subcategory"];
}
// ...
The managed object contexts are setup like so:
self.tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.tempContext.parentContext = self.dataManager.mainContext;
Eventually I have this:
[self saveTempContext];
Here is the saveContext implementation:
- (void)saveContext
{
LogAndPrint(#"Before save.");
[self.tempContext performBlockAndWait:^{
NSError *error = nil;
if (![self.tempContext save:&error])
{
LogAndPrint(#"Error occurred while saving context: %#", error);
}
}];
LogAndPrint(#"Middle of save.");
[self.dataManager.mainContext performBlockAndWait:^{
NSError *error = nil;
if (![self.dataManager.mainContext save:&error])
{
LogAndPrint(#"Error occurred while saving context: %#", error);
}
}];
[self.dataManager synchronize];
LogAndPrint(#"After save.");
}
Here is the synchronize implementation:
- (void)synchronize
{
LogAndPrint(#"Synchronizing Core Data changes.");
if (self.persistentContext.hasChanges) {
[self.persistentContext performBlockAndWait:^{
NSError *error = nil;
if (![self.persistentContext save:&error]) {
LogAndPrint(#"Error occurred while saving persistent context: %#", error);
}
}];
}
}

I was never able to figure out why this was happening. But I did find a solution that works. I changed the code that was calling setSubcategory to call a new method named [SpecialItem updateSubcategory:subcategory].
- (void)updateSubcategory:(Subcategory *)subcategory
{
self.subcategory = subcategory;
self.myProperty = subcategory.category.myProperty;
}
This fixed it and the code has been working fine for several months now.

Related

crash on coredata delete in concurrency scenario: CoreData could not fulfill a fault

We are running into many concurrency related crashes related to core data and deletes with our app, so I created a small project to reproduce one of those scenario.
I am able to reproduce a crash with "CoreData could not fulfill a fault" in the following scenario:
- I have 2 child contexts A, B, both associated with the same main parent content.
- the coredata model is very simple, one ConferenceRoom object has many Line objects.
- context A and B have concurrency type "NSPrivateQueueConcurrencyType", parent with type "NSMainQueueConcurrencyType"
- thread 1 fetches an object from child context A, faults it, deletes it in context A and the main parent context
- thread 2 fetches the same object from child context B, waits for 2 seconds, saves it in the context B and the main parent context.
-->the app crashes in thread2 with "CoreData could not fulfill a fault" when it tries to save to child context B
Note that we've already fixed issues after trying the new xcode6 coredata debug flag:"-com.apple.CoreData.ConcurrencyDebug 1 ", so no threading warning/issues in theory...
So can anyone explain how we can avoid those crashes? (I can send the full project if needed).
Here is the code (I am a novice ios developer, and it's quick/dirty code for sure)
Core methods:
//this creates a Conference and a Line object (called from AppDelegate when app launches
- (void) testCrash
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"dd-MM HH:mm:ss"];
NSString *initConfName = [formatter stringFromDate:[NSDate date]];
//fix
[self.managedObjectChildContext performBlockAndWait:^{
ConferenceRoom *room = [NSEntityDescription insertNewObjectForEntityForName:#"ConferenceRoom" inManagedObjectContext:self.managedObjectChildContext];
room.name = initConfName;
Line *line1 = [NSEntityDescription insertNewObjectForEntityForName:#"Line" inManagedObjectContext:self.managedObjectChildContext];
line1.phoneNumber = #"4154243243";
NSMutableSet *lines = [room mutableSetValueForKey:#"lines"];
[lines addObject:line1];
}];
[self saveChildContext];
[self saveContext];
NSThread* myThread = [[NSThread alloc] initWithTarget:self
selector:#selector(mainThread1:)
object:initConfName];
[myThread start];
NSThread* myThread2 = [[NSThread alloc] initWithTarget:self
selector:#selector(mainThread2:)
object:initConfName];
[myThread2 start];
}
- (void) mainThread1:(NSString *) initConfName
{
NSLog(#"started thread 1");
//GET OBJ FROM CHILD CONTEXT 1
[self.managedObjectChildContext performBlockAndWait:^{
NSArray *results = [self getConfRoom: self.managedObjectChildContext withName:initConfName];
NSLog(#"C1 conf:%#", results);
ConferenceRoom *roomFoundChild1 = [results lastObject];
NSArray *linesc1 = [[roomFoundChild1 mutableSetValueForKey:#"lines"] allObjects];
Line *linec1 = [linesc1 firstObject];
NSLog(#"LINEC1=%#", linec1);
//DELETE CONF IN CHILD CONTEXT 1:
NSLog(#"Thread1:going to delete conference %#", roomFoundChild1);
[self.managedObjectChildContext deleteObject: roomFoundChild1];
}];
NSLog(#"Thread1: before saving child context");
[self saveThisContext: self.managedObjectChildContext];
NSLog(#"Thread1: before saving main context");
//test: save in main context, works without this
[self saveContext];
}
- (void) mainThread2:(NSString*) initConfName
{
NSLog(#"started thread 2");
//GET OBJ FROM CHILD CONTEXT 2
__block NSArray *results;
__block ConferenceRoom *roomFoundChild2;
__block NSString *newName;
[self.managedObjectChildTwoContext performBlockAndWait:^{
results = [self getConfRoom: self.managedObjectChildTwoContext withName:initConfName];
NSLog(#"C2 conf\n:%#", results);
roomFoundChild2 = [results lastObject];
NSString *n = roomFoundChild2.name;
//UPDATE CONF ROOM IN CHILD CONTEXT 2
newName = [NSString stringWithFormat:#"%#-%#", initConfName, #"newName2"];
NSLog(#"Thread 2 waiting");
[NSThread sleepForTimeInterval:2];
NSLog(#"Thread 2 resuming");
roomFoundChild2.name = newName;
NSLog(#"roomFoundChild2, %#", roomFoundChild2);
}];
NSLog(#"Thread2: before saving child context");
[self saveThisContext:self.managedObjectChildTwoContext];
NSLog(#"Thread2: after saving to child context");
results = [self getConfRoom:self.managedObjectChildTwoContext withName:newName];
NSLog(#"C2 context after delete:%#", results);
NSLog(#"Thread2: before saving main context");
//test: save in main context, works without this
[self saveContext];
}
- (void)saveContext
{
// NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
[managedObjectContext performBlockAndWait:^{
if ([managedObjectContext hasChanges]) {
NSError *error = nil;
if (![managedObjectContext 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]);
abort();
};
}
}];
}
}
- (void)saveChildContext
{
// NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectChildContext;
if (managedObjectContext != nil) {
//COREDATAFLAG CHANGE
[managedObjectContext performBlockAndWait:^{
if ([managedObjectContext hasChanges]) {
NSError *error = nil;
if (![managedObjectContext 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]);
abort();
};
}
}];
}
}
- (void) saveThisContext: (NSManagedObjectContext *)ctx
{
if (ctx != nil) {
[ctx performBlockAndWait:^{
if ([ctx hasChanges]) {
NSError *error = nil;
if (![ctx 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]);
//removed abort to test..
//abort();
};
}
}];
}
}
//test: save in main context, works without this
[self saveContext];
You can't call that method from thread 2 because it is saving your main thread context, which can only be saved/edited/etc from the main thread. There are several ways of handling this, but one thing you can do is [self performSelectorOnMainThread:#selector(saveContext)];

iOS Game Center unable to send match data with NSCoding

So right now I'm working on sending the match data in a turn based game and I was using this post as a reference.
Good practices for Game Center matchData
I created a new class and it implements NSCoding. It currently only holds one variable for a NSString. This is the code for when I send the match data.
self.game.status = #"Test";
NSData *updatedMatchData = [NSKeyedArchiver archivedDataWithRootObject:self.game];
[self.currentMatch endTurnWithNextParticipants:[NSArray arrayWithObject:nextPerson]
turnTimeout:1000
matchData:updatedMatchData
completionHandler:^(NSError *error) {
if (error) {
NSLog(#"Error: %#", error);
}
}];
NSLog(#"Successfully ended turn");
}
When I try retrieving the match data, I tried this.
[match loadMatchDataWithCompletionHandler:^(NSData *matchData, NSError *error) {
if (matchData)
{
RaceGame *updatedGame = [NSKeyedUnarchiver unarchiveObjectWithData:matchData];
NSLog(#"Match Data: %#", updatedGame.status); //prints null
callback(matchData);
}
}];
However, status is null. I've checked that match isn't null either. I also printed out the match and it said that matchData.length = 135, but I kept changing things around and it was still 135 so I'm not sure if that's helpful.
Any ideas on why status isn't changing?
--EDIT--
.m
#implementation RaceGame
#synthesize status;
#pragma mark - NSCoding protocol
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:status forKey:#"status"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
self.status = [aDecoder decodeObjectForKey:#"status"];
}
return self;
}
#end
.h
#interface RaceGame : NSObject <NSCoding> {
NSString *status;
}
/* Match Data */
#property (nonatomic, copy) NSString *status;
#end
Never mind, really stupid mistake by me. I was testing it on two devices and I only ran the updated version on one of the devices.

CoreData nested contexts: what is the proper way to save context?

I am using nested contexts pattern to support multithreaded work with CoreData.
I have CoredDataManager singleton class and the inits of contexts are:
self.masterContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.masterContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
self.mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.mainContext.parentContext = self.masterContext;
For each insert operation on response from web service I use API of my CoreDataManager to get new managed context:
- (NSManagedObjectContext *)newManagedObjectContext {
NSManagedObjectContext *workerContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
workerContext.parentContext = self.mainContext;
return workerContext;
}
It looks something like (PlayerView class is subclass of NSManagedObject class):
[PlayerView insertIfNeededByUniqueKey:#"playerViewId" value:playerViewId inBackgroundWithCompletionBlock:^(NSManagedObjectContext *context, PlayerView *playerView) {
playerView.playerViewId = playerViewId;
playerView.username = playerViewDictionary[#"name"];
[context saveContextWithCompletionBlock:^{
//do something
} onMainThread:NO];//block invocation on background thread
}];
saveContextWithCompletionBlock method is implemented in NSManagedObjectContext category:
- (void)saveContextWithCompletionBlock:(SaveContextBlock)completionBlock onMainThread:(BOOL)onMainThread {
__block NSError *error = nil;
if (self.hasChanges) {
[self performBlock:^{
[self save:&error];
if (error) {
#throw [NSException exceptionWithName:NSUndefinedKeyException
reason:[NSString stringWithFormat:#"Context saving error: %#\n%#\n%#", error.domain, error.description, error.userInfo]
userInfo:error.userInfo];
}
if (completionBlock) {
if (onMainThread && [NSThread isMainThread]) {
completionBlock();
} else if (onMainThread) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
} else if ([NSThread isMainThread]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^{
completionBlock();
});
} else {
completionBlock();
}
}
}];
}
}
Then on some stage I'm calling method of CoreDataManager to save master context:
- (void)saveMasterContext; {
__block NSError *error;
[self.mainContext performBlock:^{
[self.mainContext save:&error];
[self treatError:error];
[self.masterContext performBlock:^{
[self.masterContext save:&error];
[self treatError:error];
}];
}];
}
I have two main classes, subclasses of NSManagedObject - PlayerView and Post.
PlayerView has relation one to many to Post.
PlayerView is saved and is ok. The Post is never saved and I get error:
CoreData: error: Mutating a managed object 0x17dadd80 (0x17daf930) after it has been removed from its context.
I think, that the problem is in contexts saving logic.
First of all, the error you're experiencing usually happens when the context in which you created the new managed object goes away (released) before you had the chance to save it.
Secondly, the best way to make sure the context is saved in the appropriate thread is to use performBlock or performBlockAndWait instead of trying to figure out which thread the context belongs to. Here's a sample "save" function that saves the context safely:
+ (BOOL)save:(NSManagedObjectContext *)context {
__block BOOL saved = NO;
[context performBlockAndWait: {
NSError *error;
saved = [context save:&error];
if (!saved) {
NSLog("failed to save: %#", error);
}
}]
return saved;
}
As for using nested private contexts (with main thread context as the parent), our team experienced some issues with that model (can't recall exactly what it was), but we decided to listen for NSManagedObjectContextDidSaveNotification and use mergeChangesFromContextDidSaveNotification to update contexts.
I hope this helps.
A great tutorial by Bart Jacobs entitled: Core Data from Scratch: Concurrency describes two approaches in detail, the more elegant solution involves parent/child managed object contexts, including how to properly save context.

Core data save in Background with one-to-many relationship

in a core data app with a one-to-many relationship (one "test", many "measures"), I used to have this code :
In AppDelegate.m :
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil)
return _managedObjectContext;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
In TableViewController.m :
- (NSManagedObjectContext *)managedObjectContext
{
NSManagedObjectContext *context = nil;
id contextDelegate = [[UIApplication sharedApplication] delegate];
if ([contextDelegate performSelector:#selector(managedObjectContext)])
context = [contextDelegate managedObjectContext];
return context;
}
- (void)saveEntryButton:(id)sender
{
NSManagedObjectContext *context = [self managedObjectContext];
if (self.test)
{
// Update existing test
self.test.number = self.numberTextField.text;
}
else // Create new test
{
self.test = [NSEntityDescription insertNewObjectForEntityForName:#"Test" inManagedObjectContext:context];
self.test.number = self.numberTextField.text;
}
if (isSaving)
{
NSManagedObjectContext *context = [test managedObjectContext];
self.measure = [NSEntityDescription insertNewObjectForEntityForName:#"Measure" inManagedObjectContext:context];
[test addWithMeasureObject:measure];
NSData *newDataArray = [NSKeyedArchiver archivedDataWithRootObject:plotDataArray];
self.measure.dataArray = newDataArray;
}
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error])
{
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
}
It works great, but of course, the [NSKeyedArchiver archivedDataWithRootObject:plotDataArray]; can take a few seconds and block the UI so I would like to do it in background.
I spent a few hours to read everything about the concurrency in core data (and I am quite new at it), but I didn't find anything regarding my problem : how to deal with a one-to-many relationship background save ?
What I've tried so far :
In AppDelegate.m
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil)
return _managedObjectContext;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:_persistentStoreCoordinator];
//_managedObjectContext = [[NSManagedObjectContext alloc] init];
//[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
In TableViewController.m
- (void)saveEntryButton:(id)sender
{
NSManagedObjectContext *context = [self managedObjectContext];
if (self.test)
{
// Update existing test
self.test.number = self.numberTextField.text;
}
else // Create new test
{
self.test = [NSEntityDescription insertNewObjectForEntityForName:#"Test" inManagedObjectContext:context];
self.test.number = self.numberTextField.text;
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error])
{
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
}
if (isSaving)
{
NSManagedObjectContext *context = [test managedObjectContext];
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = context;
[temporaryContext performBlock:^{
self.measure = [NSEntityDescription insertNewObjectForEntityForName:#"Measure" inManagedObjectContext:temporaryContext];
[test addWithMeasureObject:measure];
NSData *newDataArray = [NSKeyedArchiver archivedDataWithRootObject:plotDataArray];
self.measure.dataArray = newDataArray;
// push to parent
NSError *error;
if (![temporaryContext save:&error])
{
// handle error
NSLog(#"error");
}
// save parent to disk asynchronously
[context performBlock:^{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSError *error;
if (![context save:&error])
{
// handle error
NSLog(#"error");
}
}];
}];
}
}
Of course, I receive a SIGABRT error as "test" and "measure" are not in the same context...I've tried a LOT of different things, but I'm really lost.
Thanks in advance for any help.
I see two questions here: background asynchronous saving and what to do with objects in different contexts.
First about saving. Are you sure that it is saving itself that blocks your UI thread and not call to archivedDataWithRootObject? If saving itself is relatively fast, you can consider calling only archivedDataWithRootObject on a background queue, and then communicating the results back to the main queue where you’ll do the save on your UI context.
If it is still the save that takes too long, you can use the approach for background asynchronous saving recommended by Apple. You need two contexts. One context – let’s call it background – is of private queue concurrency type. It is also configured with persistent store coordinator. Another context – let’s call it UI – is of main queue concurrency type. It is configured with background context as a parent.
When working with your user interface, you’re using the UI context. So all managed objects are inserted, modified, and deleted in this context. Then when you need to save you do:
NSError *error;
BOOL saved = [UIContext save:&error];
if (!saved) {
NSLog(#“Error saving UI context: %#“, error);
} else {
NSManagedObjectContext *parent = UIContext.parentContext;
[parent performBlock:^{
NSError *parentError;
BOOL parentSaved = [parent save:&parentError];
if (!parentSaved) {
NSLog(#“Error saving parent: %#“, parentError);
}
}];
}
The save of the UI context is very fast because it doesn’t write data to disk. It just pushes changes to its parent. And because parent is of private queue concurrency type and you do the save inside performBlock’s block, that save happens in background without blocking the main thread.
Now about different managed objects in different contexts from your example. As you discovered, you can’t set an object from one context to a property of an object in another context. You need to choose a context where you need to do the change. Then transfer NSManagedObjectID of one of the objects to the target context. Then create a managed object from ID using one of the context’s methods. And finally set this object to a property of another one.
Essentially you are on the right track, but missing a couple of key elements;
Firstly you will need to transfer test from your main context to the secondary - this is done in the following way;
//this is the object saved in your main managedObjectContext;
NSManagedObjectID *currentTest = test.objectID;
creating the secondary context for adding your related objects can be performed on a background thread. You can use and NSBlockOperation to do the secondary save and create the context at the same time.
here is a simple example using the standard person / address example wired to an IBAction
- (IBAction)button1Click:(id)sender {
NSError *saveError = nil;
// create instance of person to save in our primary context
Person *newParson = [[Person alloc]initIntoManagedObjectContext:self.mainContext];
newParson.name = #"Joe";
[self.mainContext save:&saveError];
//get the objectID of the Person saved in the main context
__block NSManagedObjectID *currentPersonid = newParson.objectID;
//we'll use an NSBlockOperation for the background processing and save
NSBlockOperation *addRelationships = [NSBlockOperation blockOperationWithBlock:^{
// create a second context
NSManagedObjectContext *secondContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[secondContext setPersistentStoreCoordinator:coordinator];
NSError *blockSaveError = nil;
/// find the person record in the second context
Person *differentContextPerson = (Person*)[secondContext objectWithID:currentPersonid];
Address *homeAddress = [[Address alloc]initIntoManagedObjectContext:secondContext];
homeAddress.address = #"2500 1st ave";
homeAddress.city = #"New York";
homeAddress.state = #"NY";
homeAddress.zipcode = #"12345";
Address *workAddress = [[Address alloc]initIntoManagedObjectContext:secondContext];
workAddress.address = #"100 home Ave";
workAddress.city = #"Newark";
homeAddress.state = #"NJ";
homeAddress.zipcode = #"45612";
[differentContextPerson addAddressObject:homeAddress];
[differentContextPerson addAddressObject:workAddress];
[secondContext save:&blockSaveError];
}];
[addRelationships start];
}
in the above initIntoManagedObjectContext is a simple helper method in the NSManagedObject subclass as follows;
- (id)initIntoManagedObjectContext:(NSManagedObjectContext *)context {
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
self = [super initWithEntity:entity insertIntoManagedObjectContext:context];
return self;
}
An important note from Apple docs regarding NSBlockOperation:
You must create the managed context on the thread on which it will be used. If you use NSOperation, note that its init method is invoked on the same thread as the caller. You must not, therefore, create a managed object context for the queue in the queue’s init method, otherwise it is associated with the caller’s thread. Instead, you should create the context in main (for a serial queue) or start (for a concurrent queue).

NSmanaged context threads

I use a singleton for working with arrays etc. cross the views in the application.
To initialize the singleton and the NSManagedObjectContext, so that I can fetch objects, I use:
+(DataControllerSingleton *)singleDataController
{
static DataControllerSingleton * single=nil;
#synchronized(self)
{
if(!single)
{
single = [[DataControllerSingleton alloc] init];
NSManagedObjectContext *context = [single.fetchedResultsController managedObjectContext];
single.masterCareList = [[NSMutableArray alloc] init];
}
}
return single;
}
When I insert a new object that object will not show up in display functions until I restart the application. I insert new object through this method in the singleton class:
- (void)insertNewObject:(Care *)care
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:
[entity name] inManagedObjectContext:self.managedObjectContext];
NSString *fileName = care.pictureURL;
NSString *text = care.causeText;
NSDate *date = care.date;
NSData *imgData = care.imageData;
[newManagedObject setValue:fileName forKey:#"urlPath"];
[newManagedObject setValue:text forKey:#"name"];
[newManagedObject setValue:date forKey:#"date"];
[newManagedObject setValue:imgData forKey:#"imageData"];
// Save the context.
[self saveContext];
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]);
abort();
}
}
My count method is the way I can tell that the new object is not included until I restart the application. The count method is also in the singleton as well.
- (NSUInteger)countOfList
{
NSArray *fetchedData = [_fetchedResultsController fetchedObjects];
return [fetchedData count];
}
When calling singleton I use:
DataControllerSingleton *singletonData = [DataControllerSingleton singleDataController];
[singletonData insertNewObject:care];
managedObjectContext property:
.h:
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
.m:
#implementation DataControllerSingleton
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
Why will not my new object show up in ex count until I restart application?
Am I somehow using different threads with different contexts, or different fethedResultsController or different singleton (shouldnt be possible right?)??
I added these two lines, which are not included in the genereated CoreData Stack, and it now works fine.
In singleton header:
#interface DataControllerSingleton : NSObject <NSFetchedResultsControllerDelegate>
In implementation file,
(NSFetchedResultsController *)fetchedResultsController {
_fetchedResultsController.delegate = self;
As I understand from your question, you are using a table or similar.
If you want to update the table as soon as you save the context you need to:
Reload the data table [table reloadData];
or implement in the correct delegate methods of (take a look to How To Use NSFetchedResultsController)
If you follow the first option, you can just do a save in the context and call realod data on the table.
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]);
abort();
}
[table reloadData];
NOTE THAT YOU ARE CALLING THE SAVE TWICE Do it once. In this case I suppose that [self saveContext]; does the saving as above.
If you follow the second approach, the data reload woul be handled for you.
Hope that helps.
Edit
The delegate of your fetched results controller should be a view controller (the one that contains the table). Do not put it in your singleton!!

Resources