I am having a strange issue with MagicalRecord. Deletes will not persist. When I delete, NSFetchedResultsControllerDelegate correctly sees that the object has been deleted. However, if I close and reopen the app, the entity reappears.
The code I am using to delete the entity is:
ActivityType *activityType = [_fetchedResultsController objectAtIndexPath:indexPath];
[activityType deleteInContext:[NSManagedObjectContext MR_defaultContext]];
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
The code I am using for setting up the NSFetchedResultsController is:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"ActivityType" inManagedObjectContext:[NSManagedObjectContext defaultContext]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"name" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[NSManagedObjectContext defaultContext] sectionNameKeyPath:nil
cacheName:#"activityTypes"];
_fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
Based on other SO posts, I also tried to use [NSManagedObjectContext rootSavingContext] in both the setup and deletion (but to no avail).
I've been through hell and back with core data, and I learned a few things. I'm tired so I'll just write a quick summary.
When you delete an entity, core data may reject it due to your deletion rules. The reason why my deletes didn't go through is because it needed to be cascade but it was nullify. I think it has to do with somehow leaving entities abandoned. I don't know why that would be cause to prevent deletion, but that's what fixed it in my case. The way I discovered it was through log, I saw some statement about a referenced dependent entity, and I realized that delete rules will apply.
When the log says something about a serious error and a listener, check the FRC code. Since this is the listener, your culprit code will be here somewhere. In my case, I disabled [tableview beginUpdates] and [tableview endupdates]. The FRC actually needs this (I thought it was optional). Otherwise, you get some error about inconsistency and managedobjectcontextlistener and how rows need to be added or deleted etc. etc.
when you delete, it may actually get saved into the memory local context, but may not get saved to the persistent store. this means that the FRC delegate code will see the changes, but it may not get saved. also, the memory store may not do the deletion rules checks as it passed mine. but the persistent store will do the checks. gotta look into this more.
Related
I want to provide user a functionality to delete single or multiple messages at a time using long-tap/select action.
I know you want to know what I have tried so far. But the thing is I haven't found anything regarding deleting messages to implement.
Any kind of help is appreciated!
You have to delete message from xmpp core database.
So xmpp had created "XMPPMessageArchiving_Message_CoreDataObject" named core database table and using this you can delete message.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:myAppdelObject.Obj_xmppManager.moc];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *items = [mocObject executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *managedObject in items) {
[mocObject deleteObject:managedObject];
}
#Parthpatel1105's answer is right, though as #Bista says, it will not delete the messages permanently.
After performing the deletion, any deletion, either full deletion as #Parthpatel1105 does, or a single message, which would be the same but without the for loop and you'd have to find the single message to delete. You HAVE to save the storage context.
Which means, after doing:
for (NSManagedObject *managedObject in items) {
[mocObject deleteObject:managedObject];
}
You have to add a call to save,
In Swift (where I've used it):
mocObject.save()
In Objective-C, I think it would be something like:
[mocObject save:&error];
I'm using Core Data with MagicalRecord and NSFetchedResultsController to display data in a table view. Everything works fine, now I have to change the underlying sqlite-file underneath which leads to a crash.
I'm creating the FRC the following way:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"CDPerson"];
NSSortDescriptor *sortByNameDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"lastName" ascending:YES];
request.sortDescriptors = #[sortByNameDescriptor];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:[NSManagedObjectContext MR_contextForCurrentThread] sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
[_fetchedResultsController performFetch:nil];
When changing the sqlite-file, I'm doing:
[MagicalRecord cleanup];
// delete old sqlite file
// copy new sqlite file
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:SQLITE_FILENAME];
What do I have to do with my FRC to have it take the new storage? Only create a new one seems not enough as I get a crash in
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[[self tableView] endUpdates];
}
How can I achieve this?
EDIT
#question from flexaddicted: The error I'm getting in endUpdates is Assertion failure in -[UITableView _endCellAnimationsWithContext:]
#question from Exploring: you can imagine that I've got two sqlite files which I'm exchanging - both with the same tables, but different content. The FRC shows the content of the first file, now I'd like cleanUp MagicalRecord, let it point to the other store and 'refresh' the FRC.
You basically need to tear everything down and restart. Once you have recreated the Core Data stack you need to recreate the FRC with the new context and reloadData on the table view (you can't just reload some rows because the backing data has completely changed).
I am making chat-app and I looking the way to save and load from core data.
I save and load to it all user's history and it works good.
I am looking the way how can I load and save roster list
I am not sure here. I load user's info from web at startup by getting user's ids from roster list and request web service for that user's info. I want to save it to core data with roster list.
How can I set for every jUser (loaded from core data) his web server info? There are 2 problems here:
I can not get JUser from core data for its id
If I do 1. I can set to that user his web image and data to his core data's storied account. - I think it is not a good idea. How can I manage users here?
Some code:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController == nil)
{
NSManagedObjectContext *moc = [[self appDelegate] managedObjectContext_roster];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"XMPPUserCoreDataStorageObject"
inManagedObjectContext: moc];
NSSortDescriptor *sd1 = [[NSSortDescriptor alloc] initWithKey:#"sectionNum" ascending:YES];
NSSortDescriptor *sd2 = [[NSSortDescriptor alloc] initWithKey:#"displayName" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects: sd1, sd2, nil];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchBatchSize:10];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:moc
sectionNameKeyPath:#"sectionNum"
cacheName:nil];
[_fetchedResultsController setDelegate:self];
NSError *error = nil;
if (![_fetchedResultsController performFetch:&error])
{
//DDLogError(#"Error performing fetch: %#", error);
}
}
return _fetchedResultsController;
}
What you are showing above is for keeping track/getting the info that is populated with a request to the XMPP server for it's roster (be it an autoFetch or a manual fetch using the XMPPRoster 'fetchRoster' method (assuming you have set the CoreData way of storing your roster data, not memory).
Once the response to the roster fetch is returned (iq result), the delegates within the XMPPRoster instance will capture and put in place with the given storage option. If the server you are using conforms to the XMPP rfc, then this should happen pretty automatically on the return.
For example - in XMPPRoster.didReceiveIQ() - you can see the call to '[xmppStorage handleRosterItem:item xmppStream:xmppStream]'. This is where the processed
You can extend the storage here (XMPPRosterCoreDataStorage and XMPPUserCoreDataStorage for example) and set in place to add additional information to the entity. For example here - XMPPUserCoreDataStorage has an over-ride '(void)didUpdateWithItem:(NSXMLElement *)item' that you can define attributes in to point to another entity. Here you would copy the existing data model and add your own attributes to it - using the over-ride above to enter them.
As for the messages, depends on if a MUC or a 1:1 - but they use different managed objects as well. XEP-0045 is what is storing the MUC messages that you can try to attach to for the users last message in there - as well as XMPPMessageArchiving for the 1:1 storage, but you would still need support from the server on this if you need to persist the capturing of another users last message - unless you are only talking about per session (which you could then store locally for display).
I am using a NSFetchResultsController to display 100,000 + records in a UITableView. This works but it is SLOW, especially on an iPad 1. It can take 7 seconds to load which is torture for my users.
I'd also like to be able to use sections but this adds at least another 3 seconds onto the laod time.
Here is my NSFetchResultsController:
- (NSFetchedResultsController *)fetchedResultsController {
if (self.clientsController != nil) {
return self.clientsController;
}
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Client" inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
[request setPredicate:[NSPredicate predicateWithFormat:#"ManufacturerID==%#", self.manufacturerID]];
[request setFetchBatchSize:25];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"UDF1" ascending:YES];
NSSortDescriptor *sort2= [[NSSortDescriptor alloc] initWithKey:#"Name" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObjects:sort, sort2,nil]];
NSArray *propertiesToFetch = [[NSArray alloc] initWithObjects:#"Name", #"ManufacturerID",#"CustomerNumber",#"City", #"StateProvince",#"PostalCode",#"UDF1",#"UDF2", nil];
[request setPropertiesToFetch:propertiesToFetch];
self.clientsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil
cacheName:nil];
return self.clientsController;
}
I have an index on ManufacturerID which is used in my NSPredicate. This seems like a pretty basic NSFetchRequest - anything I can do to speed this up? Or have I just hit a limitation? I must be missing something.
First: you can use the NSFetchedResultsController's cache to speed up display after the first fetch. This should quickly go down to a fraction of a second.
Second: you can try to display the only the first screenful and then fetch the rest in the background. I do this in the following way:
When the view appears, check if you have the first page cache.
If not, I fetch the first page. You can accomplish this by setting the fetch request's fetchLimit.
In case you are using sections, do two quick fetches to determine the first section headers and records.
Populate a second fetched results controller with your long fetch in a background thread.
You can either create a child context and use performBlock: or
use dispatch_async().
Assign the second FRC to the table view and call reloadData.
This worked quite well in one of my recent projects with > 200K records.
I know the answer #Mundi provided is accepted, but I've tried implementing it and ran into problems. Specifically the objects created by the second FRC will be based on the other thread's ManagedObjectContext. Since these objects are not thread safe and belong to their own MOC on the other thread, the solution I found was to fault the objects as they are being loaded. So in cellForRowAtIndexPath I added this line:
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
object = (TapCellar *)[self.managedObjectContext existingObjectWithID:[object objectID] error:nil];
Then you have an object for the correct thread you are in. One further caveat is that the changes you make to the objects won't be reflected in the background MOC so you'll have to reconcile them. What I did was make the background MOC a private queue MOC and the foreground one is a child of it like this:
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_privateManagedObjectContext setPersistentStoreCoordinator:coordinator];
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setParentContext:_privateManagedObjectContext];
}
Now when I make changes in the main thread, I can reconcile them easily by doing this:
if ([self.managedObjectContext hasChanges]) {
[self.managedObjectContext performBlockAndWait:^{
NSError *error = nil;
ZAssert([self.managedObjectContext save:&error], #"Error saving MOC: %#\n%#",
[error localizedDescription], [error userInfo]);
}];
}
I wait for it's return since I'm going to reload the table data at this point, but you can choose not to wait if you'd like. The process is pretty quick even for 30K+ records since usually only one or two are changed.
Hope this helps those who are stuck with this!
I am using Core Data on iOS and am having problems with NSFetchRequest.
If I create an NSFetchRequest like so:
NSFetchRequest* request = [ NSFetchRequest fetchRequestWithEntityName:#"Recipe"];
and then create instances of my Recipe entity, they appear in the results of the fetch request as expected. But once I save the database, and try to reload them then my fetch request returns no results.
The database saves successfully, with no errors, and using SQLite database browser I can see that all my entities have been saved in the underlying SQLite database. But they won't load...
is there an easy way to diagnose what is happening under the hood in a NSFetchRequest so I can find out why it won't load my entities?
// load recipes unsorted
NSFetchRequest* request = [ NSFetchRequest fetchRequestWithEntityName:#"Recipe"];
request.sortDescriptors = [[NSArray alloc] init];
NSFetchedResultsController* frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.recipeDatabase.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
// fetch results empty
NSError* error = nil;
[frc performFetch:&error];
Sorry.... I found my problem. I was not opening the UIManagedDocument when the state was UIDocumentStateClosed. Strange that everything else seems to work when operating on the documents managedObjectContext, even though the document is closed.