iPad app failing to write back to core data - ios

I have a routine that checks if the last character of a string stored in a Core Data entity is a $ and if so sets a variable 'last' to remember this and then rewrites to Core Data the string with the $ removed.
It does not throw up any errors, and runs through the 'if' routine if the last char is $ but it does not write back to Core Data. Can anyone see what I am doing wrong?
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Observations"];
NSError *error = nil;
observationList = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (int loop1 = 0; loop1 < [observationList count]; loop1++)
{
NSString *classCheckActual = [[observationList objectAtIndex:loop1] valueForKey: #"obsClassName"];
NSString *last = [classCheckActual substringFromIndex:[classCheckActual length] - 1];
NSString *classCheck = #"";
if ([last isEqual: #"$"])
{
classCheck = [classCheckActual substringToIndex:[classCheckActual length] - 1];
NSManagedObject *schoolObject = [[self observationList] objectAtIndex:loop1];
[schoolObject setValue:[NSString stringWithFormat:#"%#", classCheck] forKey:#"obsClassName"];
NSError *error;
[context save:&error];
}
}

The error is that you use two different arrays: observationList in the first part and [self observationList] in the second part.

Related

Records returned from executeFetchRequest is empty if the app is run the second time

Im facing a weird data loss while using core data.
Im using the following code to save records to core data.
for (int i = 0; i < [domains count]; i++){
NSString *is_active = [[domains objectAtIndex:i] objectForKey:#"is_active"];
NSString *domain_id = [[domains objectAtIndex:i] objectForKey:#"domain_id"];
NSString *domain_name = [[domains objectAtIndex:i] objectForKey:#"domain_name"];
[context performBlockAndWait:^{
NSManagedObject *domain = [NSEntityDescription insertNewObjectForEntityForName:#"Domains" inManagedObjectContext:context];
[domain setValue:is_active forKey:#"isActive"];
[domain setValue:domain_id forKey:#"domainId"];
[domain setValue:domain_name forKey:#"domainName"];
}];
}
Im using the following code to fetch records.
NSError *error;
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Domains"];
self.domainContext = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
NSLog(#"domain context %#",self.domainContext);
Im using the real device to do the testing.
if im running the app in my device for the first time, self.domainContext has got some contents in it. where as if im running the app the second time without closing the app manually, the self.domainContext doesnt have any contents within it. However if I close the app manually ans re run the app, self.domainContext has got the contents saved. Why is that so? The data is being persisted to the disk only if i close my app manually. If I run the app from xcode without closing my app manually, data is not being persisted.
You have not save the context.So that you lost the data on second time. Change your code like this
for (int i = 0; i < [domains count]; i++){
NSString *is_active = [[domains objectAtIndex:i] objectForKey:#"is_active"];
NSString *domain_id = [[domains objectAtIndex:i] objectForKey:#"domain_id"];
NSString *domain_name = [[domains objectAtIndex:i] objectForKey:#"domain_name"];
[context performBlockAndWait:^{
NSManagedObject *domain = [NSEntityDescription insertNewObjectForEntityForName:#"Domains" inManagedObjectContext:context];
[domain setValue:is_active forKey:#"isActive"];
[domain setValue:domain_id forKey:#"domainId"];
[domain setValue:domain_name forKey:#"domainName"];
}];
NSError *error = nil;
[context save:&error];
}
Hope this will help you.

CoreData insertNewObjectForEntityForName and executeFetchRequest return nil

I'll try to expose my problem, because is a bit complex.
I use Core Data and I have a problem with the data stored.
When I use this code:
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"ItemMessage"];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
NSMutableArray *values = [[NSMutableArray alloc] init];
if (error == nil) {
for (int i = 0; i<results.count; i++) {
NSLog(#"results %#",[results objectAtIndex:i]);
ItemMessage *itemMessage = [results objectAtIndex:i];
[values addObject:itemMessage];
}
ecc. the problem is that the value printed by NSLog is correct (the "results" contains something) but the itemMessage contains always 0 key/value pairs (it seems empty).
To understand what is the problem I went back and saw that in insertNewObjectForEntityForName I have also this problem, this is the code that I used when I save the messages data in Core Data:
for (id key in objectMessage) {
ItemMessage *itemmessage = [[ItemMessage alloc] init];
itemmessage.itemMessageId = [key objectForKey:#"itemMessageId"];
itemmessage.message = [key objectForKey:#"message"];
itemmessage.sender = [key objectForKey:#"sender"];
itemmessage.users = [key objectForKey:#"users"];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *newMessage;
newMessage = [NSEntityDescription insertNewObjectForEntityForName:#"ItemMessage" inManagedObjectContext:context];
[newMessage setValue: itemmessage.itemMessageId forKey:#"itemMessageId"];
[newMessage setValue: itemmessage.message forKey:#"message"];
[newMessage setValue: itemmessage.sender forKey:#"sender"];
[newMessage setValue: itemmessage.users forKey:#"users"];
[context save:&error];
if (error != nil) {
NSLog(#"Coredata error");
}
The problem is that newMessage after the insertNewObjectForEntityForName and the setValue contains also 0 key/value pairs.
Can you help me?
You don't seem to insert the new managed objects correctly into the context.
It should be:
for (id key in objectMessage) {
NSManagedObjectContext *context = [appDelegate managedObjectContext];
ItemMessage *itemmessage = (ItemMessage*)[NSEntityDescription insertNewObjectForEntityForName:#"ItemMessage"
inManagedObjectContext:context];
itemmessage.itemMessageId = [key objectForKey:#"itemMessageId"];
itemmessage.message = [key objectForKey:#"message"];
itemmessage.sender = [key objectForKey:#"sender"];
itemmessage.users = [key objectForKey:#"users"];
}
//save your inserts
To create a class file for your managed objects you could:
Go to your model file (xcdatamodeld) ->
select an entity ->
from the menu select:
Editor-> Create NSManagedObjectSubclass -> select the entities your like class files for.
Now you will have managed objects you could access with ease (NSManagedObject subclass) and benefit from CoreData features.
When you insert to manage object contest you have to call save: method, also the saving method should looks something like that:
newMessage = [NSEntityDescription insertNewObjectForEntityForName:#"ItemMessage" inManagedObjectContext:context];
// 2
newMessage.property1 = self.firstNameTextfield.text;
newMessage.property2 = self.lastNameTextfield.text;
if (![context save:&error]) {
NSLog(#"Error: %#", [error localizedDescription]);
}

How does this code use only updates into core data?

This is a SyncEngine from an RW tutorial. I need help understanding how only UPDATED records from the web are fetched and processed into Core Data.
- (void)processJSONDataRecordsIntoCoreData {
NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext];
// Iterate over all registered classes --- CHECK!
for (NSString *className in self.registeredClassesToSync) {
if (![self initialSyncComplete]) {
NSDictionary *JSONDictionary = [self JSONDictionaryForClassWithName:className];
NSArray *records = [JSONDictionary objectForKey:#"results"];
for (NSDictionary *record in records) {
[self newManagedObjectWithClassName:className forRecord:record];
}
} else {
NSArray *downloadedRecords = [self JSONDataRecordsForClass:className sortedByKey:#"objectId"];
if ([downloadedRecords lastObject]) {
NSArray *storedRecords = [self managedObjectsForClass:className sortedByKey:#"objectId" usingArrayOfIds:[downloadedRecords valueForKey:#"objectId"] inArrayOfIds:YES];
int currentIndex = 0;
//if dl count is < current index, there is an updated object dl from the web
for (NSDictionary *record in downloadedRecords) {
NSManagedObject *storedManagedObject = nil;
//Quick check to see if they indeed match, if they do, update the stored object with remote service objects
if ([storedRecords count] > currentIndex) {
storedManagedObject = [storedRecords objectAtIndex:currentIndex];
}
//Othwerwise its a new object and you need to create a new NSManagedObject to represent it in CDdb
if ([[storedManagedObject valueForKey:#"objectId"] isEqualToString:[record valueForKey:#"objectId"]]) {
[self updateManagedObject:[storedRecords objectAtIndex:currentIndex] withRecord:record];
} else {
[self newManagedObjectWithClassName:className forRecord:record];
}
currentIndex++;
}
}
}
// After all NSMO are created in your context, save it!
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"Unable to save context for class %#", className);
}
}];
// Cleanup time
[self deleteJSONDataRecordsForClassWithName:className];
[self executeSyncCompletedOperations];
}
[self downloadDataForRegisteredObjects:NO];
}
From what I understand, on the first or initial sync, it fetches JSONDictionaryForClassWithName which reads the downloaded data from disk and creates a newManagedObjectWithClassName.
My confusion is in the update else block. downloadedRecords is populated from JSONDataRecordsForClass which simply calls JSONDictionaryForClassWithName. Then it checks to see if there is at least 1 object in that array. If there is it does this:
NSArray *storedRecords = [self managedObjectsForClass:className sortedByKey:#"objectId" usingArrayOfIds:[downloadedRecords valueForKey:#"objectId"] inArrayOfIds:YES];
This fetches all managedObjectsForClass:sortedByKey which is below:
- (NSArray *)managedObjectsForClass:(NSString *)className sortedByKey:(NSString *)key usingArrayOfIds:(NSArray *)idArray inArrayOfIds:(BOOL)inIds {
__block NSArray *results = nil;
NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:className];
NSPredicate *predicate;
if (inIds) {
predicate = [NSPredicate predicateWithFormat:#"objectId IN %#", idArray];
} else {
predicate = [NSPredicate predicateWithFormat:#"NOT (objectId IN %#)", idArray];
}
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:
[NSSortDescriptor sortDescriptorWithKey:#"objectId" ascending:YES]]];
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
}];
return results;
}
The next bit which compares the [storedRecords count] > currentIndex is confusing. Can someone please explain this? I think my confusion lies in what the managedObjectsForClass method does with the usingArraysOfIds & inArrayOfIds.
I would expect that at some point it gets the the updatedAt field from the downloaded records and compares it to the updatedAt field of the CoreData fetched records.
This function is processing the stored JSON. The actual remote fetching and updateAt checking happens in downloadDataForRegisteredObjects and mostRecentUpdatedAtDateForEntityWithName.
[storedRecords count] > currentIndex is a bit crazy. Although in defense of the original programmer, writing any decent syncengine will quickly make you go googoo. Basically he needs to work out which records are existing and which ones are new and update the local data store accordingly, that's all.
I had another look and this code is actually horribly broken. It will only works if either you have the same records both locally and remotely. Or if the new objects have an objectId that sort-wise comes after the last object the local store has. Which is not the case with Parse objectId's.
If you are testing with just one device this works because new objects will be inserted locally before being pushed to the server. Therefor you will always have the same amount of records. If additional records get inserted any other way, this code will do weird things.

Core Data Multiple Item Edit Efficiency and Random Error

I hope this isn't a duplicate question. I can't seem to find anything similar. Most core data questions seem to be about new object creation...
I have a program with a database of around 23,000 items. I'm attempting to create an export/import function to send data to other devices (not linked with iCloud).
The export works just fine, as does the email...
I have the import functioning, but it functions slowly (and, more on this later, doesn't seem to work well with the iPhone 5 or iPad 3)
I have a function that parses the data I'm importing into an NSArray (_importedRows), then I run the following code:
self.managedObjectContext = [(AppDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSManagedObjectContext *ctx = self.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"CHECKLIST"
inManagedObjectContext:ctx];
[fetchRequest setEntity:entity];
ImportedData *importedData;
NSString *verify;
NSError *error = nil;
NSManagedObject *updatedObject;
NSArray *matchingItems;
for (int i = 0; i < [_importedRows count]; i++) {
importedData = [_importedRows objectAtIndex:i];
verify = importedData.uniqueID;
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"uniqueID == %#", verify]];
[fetchRequest setFetchLimit:1];
matchingItems = [ctx executeFetchRequest:fetchRequest error:&error];
for (updatedObject in matchingItems) {
HUD.detailsLabelText = [NSString stringWithFormat:#"Updating %#" , [updatedObject valueForKey:#"figureName"]];
[updatedObject setValue:importedData.numberOwned forKey:#"numberOwned"];
[updatedObject setValue:importedData.numberWanted forKey:#"wishList"];
[updatedObject setValue:importedData.availableTrade forKey:#"tradeList"];
}
[ctx save:&error];
if (error != nil) {
NSLog(#"error saving managed object context: %#", error);
}
}
Basically, I'm grabbing a core data entity, and then looping through my array checking for matches. When I find a match (the uniqueID predicate), I'm updating the object with the imported data. This code works fine on my iPhone 4s, but rather slowly. 4,000 items takes around 4-5 minutes. Am I doing anything blatantly wrong? Should I be calling the save function more frequently?
As a bonus, for some reason this code almost never works when I test it on an iPhone 5. 9 times out of 10 (and 50% of the time on my iPad 3) I get a
"Jan 14 08:06:44 : * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet addObject:]: attempt to insert nil'"
in the console. Thoughts?
Let me know if more details are needed!
UPDATE:
It seems that handleOpenURL is being called twice... once in applicationdidfinishlaunching
NSURL *url = (NSURL *)[launchOptions valueForKey:UIApplicationLaunchOptionsURLKey];
if (url != nil && [url isFileURL]) {
[self.window.rootViewController performSelector:#selector(showWithLabel:) withObject:url afterDelay:6];
}
and once here:
-(BOOL) application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{ if (url != nil && [url isFileURL]) {
[self.window.rootViewController performSelector:#selector(showWithLabel:) withObject:url];
}
return YES;
}
I have to lave both those in the app delegate, otherwise the function won't always get called (once is for when the application launches, and once if it the application was already in the background, I believe) - I've added a check to prevent it from launching a second time within the showWithLabel thread, but it doesn't seem like that is a very elegant solution...
UPDATE: #mundi advised cleaning up the fetchedresults code as follows:
NSArray *importedIDs = [_importedRows valueForKeyPath:#"uniqueID"];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"CHECKLIST"
inManagedObjectContext:ctx];
fetchRequest.predicate = [NSPredicate predicateWithFormat:
#"uniqueID in %#", importedIDs];
NSError *error = nil;
NSManagedObject *updatedObject;
NSArray *matchingItems;
matchingItems = [ctx executeFetchRequest:fetchRequest error:&error];
ImportedData *importedData;
for (int i = 0; i < [_importedRows count]; i++) {
importedData = [_importedRows objectAtIndex:i];
for (updatedObject in matchingItems) {
if ([importedData.uniqueID isEqualToString:[updatedObject valueForKey:#"uniqueID"]]) {
HUD.detailsLabelText = [NSString stringWithFormat:#"Updating %#" , [updatedObject valueForKey:#"figureName"]];
[updatedObject setValue:importedData.numberOwned forKey:#"numberOwned"];
[updatedObject setValue:importedData.numberWanted forKey:#"wishList"];
[updatedObject setValue:importedData.availableTrade forKey:#"tradeList"];
}
}
}
[ctx save:&error];
I'm sure it could still be a little cleaner and the actual updating portion (I'm not sure how to do it other than compare each item in the fetchedresults with each item in the initial array to make sure they are updated correctly, but the combined fetchedresults increased the speed tremendously (originally 240 seconds for 4000 items, now between 80-120 seconds)
sorting arrays first, then updating in order speeds it up tremendously yet again:
NSArray *matchingItemsSorted;
matchingItemsSorted = [matchingItems sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSString *first = [a valueForKey:#"uniqueID"];
NSString *second = [b valueForKey:#"uniqueID"];
return [first caseInsensitiveCompare:second];
}];
NSArray *importedRowsSorted;
importedRowsSorted = [_importedRows sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSString *first = [a valueForKeyPath:#"uniqueID"];
NSString *second = [b valueForKeyPath:#"uniqueID"];
return [first caseInsensitiveCompare:second];
}];
int i = 0;
for (updatedObject in matchingItemsSorted) {
NSLog(#"do we match? %# : %#", [[importedRowsSorted objectAtIndex:i] valueForKeyPath:#"uniqueID"], [updatedObject valueForKey:#"uniqueID"]);
HUD.detailsLabelText = [NSString stringWithFormat:#"Updating %#" , [updatedObject valueForKey:#"figureName"]];
[updatedObject setValue:[[importedRowsSorted objectAtIndex:i] valueForKeyPath:#"numberOwned"] forKey:#"numberOwned"];
[updatedObject setValue:[[importedRowsSorted objectAtIndex:i] valueForKeyPath:#"numberWanted"] forKey:#"wishList"];
[updatedObject setValue:[[importedRowsSorted objectAtIndex:i] valueForKeyPath:#"availableTrade"] forKey:#"tradeList"];
i++;
}
13 seconds or so for 4000 items with the nslog there... the only weird thing now is that when I comment out the nslog, it frequently crashes... is it happening so fast it's breaking core data - when it doesn't crash, it only takes about 4 seconds?
Thanks,
Zack
You have two nested loops. Use this pattern to speed this up:
NSArray *importedIDs = [_importedRows valueForKeyPath:#"uniqueID"];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"CHECKLIST"
inManagedObjectContext:ctx];
fetchRequest.predicate = [NSPredicate predicateWithFormat:
#"uniqueID in %#", importedIDs];
Like this you can fetch one array with all matching items.

Core Data + 'statement is still active'

I'm having a real bad time with this core data error.
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'statement is still active'
My app and code all works fine except occasionally when calling requests very quickly. It happens when I'm trying to break the app.
Going from one screen to the next, downloading data and executing fetch requests.
I know it has something to do with threading and core data.
I'm calling this piece of code from a background thread, with it's own managed object context.
+ (AN_User *)updateWithRecord:(NSDictionary *)record moc:(NSManagedObjectContext *)moc{
NSNumber *userID = nil;
NSString *username = nil;
if([record objectForKey:#"user_id"]){
userID = [NSNumber numberWithInt:[[record objectForKey:#"user_id"] intValue]];
}else if([record objectForKey:#"id_member"]){
userID = [NSNumber numberWithInt:[[record objectForKey:#"id_member"] intValue]];
}
if([record objectForKey:#"username"]){
username = [NSString stringWithFormat:#"%#", [record objectForKey:#"username"]];
}else if([record objectForKey:#"member_name"]){
username = [NSString stringWithFormat:#"%#", [record objectForKey:#"member_name"]];
}
if(!userID||!username){
return nil;
}
__block AN_User *user = nil;
[moc performBlockAndWait:^{
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"AN_User" inManagedObjectContext:moc];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(user_id == %#) OR (username == %#)", userID, username];
[request setPredicate:predicate];
if([moc countForFetchRequest:request error:nil]==0){
user = (AN_User *)[NSEntityDescription insertNewObjectForEntityForName:#"AN_User" inManagedObjectContext:moc];
}else{
NSArray *fetchResults = [moc executeFetchRequest:request error:nil];
if(fetchResults.count>0){
user = [fetchResults objectAtIndex:0];
}
}
if(user){
user.user_id = userID;
user.username = username.lowercaseString;
//Parse profile image url
NSString *avatar = [record objectForKey:#"avatar"];
NSString *fileName = [record objectForKey:#"filename"];
if([avatar isKindOfClass:[NSString class]]&&avatar.length>0){
user.profile_image_url = [NSString stringWithFormat:#"%#", avatar];
}else if([fileName isKindOfClass:[NSString class]]&&fileName.length>0){
user.profile_image_url = [NSString stringWithFormat:#"http://www.example.com/forum/avs/%#", fileName];
}
if([record objectForKey:#"gpbp_respect"]){
user.respect = [NSNumber numberWithFloat:[[record objectForKey:#"gpbp_respect"] floatValue]];
}
}
}];
return user;
}
I understand it's probably hard to tell from just this, but can anyone tell me if I'm doing anything wrong, with these requests, that is immediately obvious.
If you scroll a table that calls core data on a b/g thread, it happens and Core Data expects to have the context all on one thread.
Another SO poster worked around this by creating a MOContext per thread, but I don't like the idea of CRUD on multiple threads so I just put a dispatch_async (dispatch_get_main_queue(), ) wrapping function around my code. So far no crashes, but it's rare so I am not absolutely certain on this.

Resources