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.
Related
I have an NSManagedObject (User) in database. Then I'm trying to fetch that object from database and update field firstName:
NSFetchRequest *fetchR = [NSFetchRequest fetchRequestWithEntityName:#"User"];
NSError *err = nil;
NSArray *allUsers = [self.managedObjectContext executeFetchRequest:fetchR error:&err];
TMUser *profile = allUsers.firstObject;
[profile setValue:#"Username" forKey:#"firstName"];
[self.managedObjectContext save:&err];
if (err) {
NSLog(#"Error: %#", err.localizedDescription);
}
The code passes without errors. But if I relaunch my app, fetch request retunrs user without updated field "firstName". I have only 1 NSManagedObjectContext. All Core Data stack was initialized successfully. After fetch my user is:
Printing description of allUsers:
<_PFArray 0x14ed6600>(
ID:3451
firstName:Johnatan
lastName:Hike
phone:380995046960
email:igor#email.com
language:en
)
For some reason object changes wasn't registered in context(Context hasChanges = NO before save). What am I doing wrong? Please, help
I think you are not saving the master context.
Please check that you call:
[managedObjectContext save:&error];
on all child contexts that save the data,
and after that on the master context as well.
You have one global function(in AppDelegate) saveContext which saves everything and which I can call from anywhere safely.
I solved my problem. I recreated NSManagedObject subclass from xcdatamodeld scheme and it works. I found that if I add another properties(readonly etc.), not related to data model scheme or change property type from NSNumber(aka bool) to BOOL, it stops updating existed objects in database.
I'm using three-leveled multithreaded Core Data introduced by Marcus Zarra in his book. tempMOC for dealing with difficult tasks, mainMOC for UI management and writerMOC for writing data to persistant store.
I'm trying to integrate this model with my UITableView.
Everytime user pull-to-refresh I process downloading, parsing and loading this data. In this process there is one extra step - deleting previos entries for entit. I want this to be smooth so, the current UITableView (MOC as well) will wait to be cleaned until the last moment so this gap between deleting and loading new data wouldn't last for c.a 4 sec.
Here is my method which is called everytime I pull-to-refresh (I've removed parsing to keep the code cleaner):
- (void)loadTimetableToCoreData:(id)timetable
{
[self.pullToRefresh finishLoading];
// Initializing temporary context
NSManagedObjectContext *tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
tempContext.parentContext = self.moc;
[tempContext performBlock:^{
// Parsing JSON data
}];
[self deleteAllObjects:#"Timetable"];
NSLog(#"Finished loading to temp MOC");
[tempContext performBlock:^{
// Saving procedure with multithreading
NSError *error;
if (![tempContext save:&error]) {
NSLog(#"Couldn't save: %#", [error localizedDescription]);
}
NSLog(#"Finished saving to temp MOC");
[self.moc performBlock:^{
// Save groups to presistant store
NSError *error;
if (![self.moc save:&error]) {
NSLog(#"Couldn't save: %#", [error localizedDescription]);
}
NSLog(#"Finished saving to main MOC");
[self.writer performBlock:^{
// Save groups to presistant store
NSError *error;
if (![self.writer save:&error]) {
NSLog(#"Couldn't save: %#", [error localizedDescription]);
}
NSLog(#"Finished saving to writer MOC");
}];
}];
}];
}
I've also put some logs and if you run this code it's like:
2014-04-08 09:55:28.349 devPlan[21125:1803] Finished loading to temp MOC
2014-04-08 09:55:33.145 devPlan[21125:1803] Finished saving to temp MOC
2014-04-08 09:55:33.650 devPlan[21125:60b] Finished saving to main MOC
2014-04-08 09:55:33.652 devPlan[21125:60b] Finished saving to writer MOC
So as you can see there is this gap between loading and saving to temp MOC. It's okey because there is a lot of work going on but I would like to wait with [self deleteAllObjects:#"Timetable"]; until this work is done. When this is executed it wipes all data and when it's realoaded again it's displayed in UITableView - but still with time gap in which UITableView is empty...
What should I do to resolve thing thing? Below is the list of what I've tried so far:
Putting this delete method in various places across download method.
Messing around with MOCs performBlock: and performBlockAndWait:.
Fetching and initializing deleteMOC with NSFetchRequest, waiting for data being processed and later call delete method.
I must tell you, I'm stuck with this one... And it bothers me so much but I think there must be a logical explanation to this!
Edit
Here is the code of the method responsible for deleting:
- (void)deleteAllObjects:(NSString *)entityDescription
{
// Initializing temporary context
NSManagedObjectContext *tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
tempContext.parentContext = self.moc;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityDescription inManagedObjectContext:tempContext];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *items = [tempContext executeFetchRequest:fetchRequest error:&error];
[tempContext performBlockAndWait:^{
for (NSManagedObject *managedObject in items) {
[tempContext deleteObject:managedObject];
}
NSError *error;
if (![tempContext save:&error]) {
NSLog(#"Error deleting %# - error:%#", entityDescription, [error localizedDescription]);
}
}];
}
First the easy answer; I would put the -deleteAllObjects after the save of the tempContext.
But I would question the idea of deleting everything from the table. Could you not just remove the objects that need to be removed, add the objects that need to be added and update the ones that need to be updated? By capturing the notification from NSManagedObjectContextDidSaveNotification you can resolve that data and present (imho) the data more cleanly.
Update
I am not concerned with the parsing (insert, update, delete) part of it as who cares how long it takes. It is human perceivable no matter what so we are not going to do magic there. I am thinking more of the UX of the table view disappearing and then reappearing as opposed to individual cells updating and resorting themselves. I personally feel that is a far cleaner, slicker looking experience.
The doubling up sounds like there is something wonky in your NSFetchedResultsController delegate methods or in your -deleteAllObjects. Care to post that?
Update
Ok so you are literally deleting everything from that table. Is there a reason you are deleting vs. merging?
Now that I understand a bit more, I would do this order of events:
Process the data
Block the UITableView from updating with a -beginUpdates
Delete all data
Save both contexts
Unblock the UITableView with a -endUpdates
That should give you a more "instantaneous" refresh of the tableview.
I am not a fan of the delete/insert way of handling data but it may be appropriate in your case.
With a little help of my older code I've came onto this:
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:#"Timetable"];
[r setIncludesPendingChanges:NO];
NSArray *existingTimetables = [tempContext executeFetchRequest:r error:nil];
for (Timetable *table in existingTimetables) {
[tempContext deleteObject:table];
}
So I'm fetching Timetable and not messing it with pending changes inside tempContext. With this code everything works like it should but is blocking main thread! But I guess this qualifies to be another question.
I am trying to add objects to a Core Data Database using the following code:
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] init];
managedObjectContext.persistentStoreCoordinator = [[CoreDataController sharedCoreDataController] persistentStoreCoordinator];
Feed *feed = [NSEntityDescription insertNewObjectForEntityForName:#"Feed" inManagedObjectContext:managedObjectContext];
feed.feedID = dictionary[#"feed_id"];
feed.siteURL = dictionary[#"site_url"];
feed.title = dictionary[#"title"];
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"Error, couldn't save: %#", [error localizedDescription]);
}
Feed is a subclass of NSManagedObject. The persistentStoreCoordinator returned from the sharedCoreDataController (a singleton) is the persistentStoreCoordinator from a UIManagedDocument (created or opened when the app launches). As far as I can tell, the document is being created or opened successfully. I am running this code in the simulator, and I'm looking in the directory in which I am saving the Database (the apps Documents directory), but the persistentStore file is not being updated to reflect the new objects being added. Am I doing something wrong? I should also point out that the above code is being executed multiple times on a concurrent, asynchronous queue.
Any help would be much appreciated, thanks in advance.
Update: After the suggestions from Alexander and Duncan, the code above has been updated to reflect the changes. Sadly, however, I haven't noticed any difference (the new data is not appearing in the persistentStore file).
Have you called the line to save your managedObjectContext? How about this:
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"Error, couldn't save: %#", [error localizedDescription]);
}
Try using something like this
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] init];
managedObjectContext.persistentStoreCoordinator = [[CoreDataController sharedCoreDataController] persistentStoreCoordinator];
for (NSDictionary *dictionary in arrayOfData) {
Feed *feed = [NSEntityDescription insertNewObjectForEntityForName:#"Feed" inManagedObjectContext:managedObjectContext];
feed.feedID = dictionary[#"feed_id"];
feed.siteURL = dictionary[#"site_url"];
feed.title = dictionary[#"title"];
}
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"Error, couldn't save: %#", [error localizedDescription]);
}
I would avoid creating a managedObjectContext for each object you insert.
First, using UIManagedDocument is not recommended. It is not intended to be your single app wide context. It is meant for document style applications.
Second, the NSManagedObjectContext that is exposed from the UIManagedDocument doesn't have a NSPersistentStoreCoordinator attached to it. It is a child context of a private NSManagedObjectContext that then has a NSPersistentStoreCoordinator. I suspect that if you used the debugger you may find that your NSManagedObjectContext is missing its NSPersistentStoreCoordinator.
In any event, you are using UIManagedDocument and then trying to attach another NSManagedObjectContext to the same NSPersistentStoreCoordinator. That is a bad design and you should at a minimum remove the UIManagedDocument or stop creating a new NSManagedObjectContext.
What is the point of the new context? What are you trying to solve with this code?
I have a CoreData entity Tracker which stores the dates.
The app receives a notification and the CheckListViewController enters data in CoreData for up to 13 days, so when the CheckListViewController gets dismissed, the CoreData entity Tracker will be filled with 13 rows.
In the MainViewController (which dismisses CheckListViewController), I have the following code:
- (void)dataSaved {
self.checkListVC dismissViewControllerAnimated:YES completion:^{
// fetching all the data from 'Tracker' entity and doing NSLog on it
// all data gets logged in console without any issues
}];
}
Now, after that somewhere in my code, I fetch all the data from the entity Tracker but the return data is empty. The CoreData doesn't show any error it simply returns and empty array.
Edit:
Code to fetch results from CoreData
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:ENTITY];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
[request setSortDescriptors:#[sortDescriptor]];
request.predicate = (fromDate && toDate) ? [NSPredicate predicateWithFormat:#"date >= %# AND date <= %#", fromDate, toDate] : nil;
__block NSArray* fetchedHabits;
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
fetchedHabits = [managedObjectContext executeFetchRequest:request error:&error];
if (error) NSLog(#"Unknown error occurred while fetching results from CoreData, %# : %#", error, [error userInfo]);
}];
CoreData model:
Update 1:
So as you can see there are two entities, namely Habit and Tracker. When I fetch results from Habit it all works fine, but when I try to fetch results from Tracker it gives me an empty array. I have a common NSManagedObjectContext instance because you can manage multiple CoreData entities with single managedObjectContext.
I have checked managedObjectContext.persistentStoreCoordinator.managedObjectModel.entitiesByName and it also lists both the entities.
Update 2:
Code where I add data in to Tracker
TrackerCoreData *tracker = [NSEntityDescription insertNewObjectForEntityForName:ENTITY
inManagedObjectContext:managedObjectContext];
tracker.date = date;
tracker.habits = habits;
// saving CoreData explicitly
NSError *error = nil;
[managedObjectContext save:&error];
There could be many reasons for your failure to display the records:
data was not saved
data was not retrieved correctly
data was not displayed correctly
All of these could be potentially complicated scenarios, but you should check them in this order.
A much better approach: use NSFetchedResultsController for your main view controller and have the delegate methods take care of updating your table view. No need to fetch, no work to be done in any completion methods - just save the data and the FRC will update your table.
Edit: how to check the physical database
It is possible that your data only exists in memory but is not actually saved to the database. Find the actual database file (in the documents folder of the app from the Simulator) and check it with the sqlite3 command line utility, or with the Firefox plugin "SQLite Manager".
Edit2: more concrete recommendations
You should make sure that you call:
[managedObjectContext save:&error];
Also double-check what your ENTITY macro stands for (not a very smart name).
It seems to me that you are overusing the block methods to no apparent purpose. First try to make everything work on the main thread (one context!). Only if you get performance problems consider background threads and context and calls to performBlock etc.
Let's say I load in 1,000 objects via Core Data, and each of them has a user-settable Favorite boolean. It defaults to NO for all objects, but the user can paw through at will, setting it to YES for as many as they like. I want a button in my Settings page to reset that Favorite status to NO for every single object.
What's the best way to do that? Can I iterate through every instance of that Entity somehow, to set it back? (Incidentally, is 'instance' the right word to refer to objects of a certain entity?) Is there a better approach here? I don't want to reload the data from its initial source, since other things in there may have changed: it's not a total reset, just a 'Mass Unfavourite' option.
Okay, I've gotten it to work, but it requires a restart of the app for some reason. I'm doing this:
- (IBAction) resetFavourites: (id) sender
{
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
[fetch setEntity: [NSEntityDescription entityForName: #"Quote" inManagedObjectContext: [[FQCoreDataController sharedCoreDataController] managedObjectContext]]];
NSArray *results = [[[FQCoreDataController sharedCoreDataController] managedObjectContext] executeFetchRequest: fetch error: nil];
for (Quote *quote in results) {
[quote setIsFavourite: [NSNumber numberWithBool: NO]];
}
NSError *error;
if (![self.fetchedResultsController performFetch: &error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[self.tableView reloadData];
}
This works fine if I close and re-open the app, but it isn't reflected immediately. Isn't that what the reloadData should do, cause an immediate refresh? Am I missing something there?