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.
Related
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.
I have a JSON request which returns different parameters, name for example. I would like to cache a variable for the name parameter, which I can view in the app later.
For example: name from JSON request = david. the variable is called firstname. firstname should equal "david". firstname should be stored so I can view it in all parts of my apps.
For something simple as a string, the quick and dirty solution is to store it in NSUserDefaults.
Storing
[[NSUserDefaults standardDefaults] setObject:firstname forKey:#"kUserFirstName"];
[[NSUserDefaults standardDefaults] synchronize];
Retrieving
NSString *string = [[NSUserDefaults standardDefaults] objectforKey:#"kUserFirstName"];
If it gets more complicated than that, you have to consider a more structured persistence store. A valid option is CoreData.
There exist a few frameworks that might help you in storing JSON resources in CoreData, the most interesting being RestKit.
First off, you might consider checking out RestKit as it successfully accomplishes a whole slew of server interaction and CoreData persistence in iOS.
I'm a little short on time (on a lunch break here) so I'll just lazily post an example from an app I have.
- (void)loadFiltersFromJSON {
NSString *path = [[NSBundle mainBundle] pathForResource:#"FreeFilterBank" ofType:#"json"];
NSData *filterData = [NSData dataWithContentsOfFile:path];
NSError *err;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:filterData options:kNilOptions error:&err];
if (err) { //TODO this can be removed prior to shipping the app
NSLog(#"%#", err);
}
NSArray *definedFilters = [json objectForKey:#"Filter"];
NSManagedObjectContext* moc = [self managedObjectContext];
for (NSDictionary *filter in definedFilters) {
NSString *name = [filter valueForKey:#"name"];
BOOL exists = [self alreadyExists:name inManagedObjectContext:moc];
if (!exists) {
NSString *imageNamed = [filter valueForKey:#"imageName"];
NSString *filterDesignator = [filter valueForKey:#"filterDesignator"];
NSString *paid = [filter valueForKey:#"paidOrFree"];
[self createFilterWithName:name imageNamed:imageNamed filterDesignator:filterDesignator paidOrFree:paid];
}
}
}
- (BOOL)alreadyExists:(NSString*)filterNamed inManagedObjectContext:(NSManagedObjectContext*)moc {
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"name == %#", filterNamed];
NSEntityDescription* description = [NSEntityDescription entityForName:#"Filter" inManagedObjectContext:moc];
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity:description];
[request setPredicate:predicate];
NSError* error;
NSArray* fetchedResult = [moc executeFetchRequest:request error:&error];
if (error) {
NSLog(#"%#",error.localizedDescription);
}
if (fetchedResult.count == 0) {
return NO;
}
else {
return YES;
}
}
- (void)createFilterWithName:(NSString*)name imageNamed:(NSString*)imageName filterDesignator:(NSString*)designator paidOrFree:(NSString *)paid {
NSManagedObjectContext* moc = [self managedObjectContext];
Filter* newFilter = [NSEntityDescription insertNewObjectForEntityForName:#"Filter" inManagedObjectContext:moc];
newFilter.name = name;
newFilter.imageName = imageName;
newFilter.filterDesignator = designator;
newFilter.paidOrFree = paid;
NSError* error;
[moc save:&error];
if (error) {
NSLog(#"%#",error.localizedDescription);
}
}
TL;DR This loads data from a JSON stored in the bundle, checks the SQLite data store to see if we already have something with the same name, and creates a new persistent instance of this object if we don't.
Take this example for what you will, there are many many more invocations for serialized data pulled from the web and persistent data within iOS beyond this one example.
The easiest way is to use NSUserDefaults and set the key to #"firstname" and the value would be #"david". That being said, you might consider using a better persistence model like CoreData. You can also use an Sqlite database or have the key/value saved in a plist. There are a number of ways to do this.
For reference ,see this:
Save string to the NSUserDefaults?
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.
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.
I have the following method in my UITableViewController subclass:
-(void)populateDataStorage{
NSString *path = [[NSBundle mainBundle] pathForResource:#"FakeData" ofType:#"plist"];
if(path){
NSArray *plistData = [[NSArray alloc] initWithContentsOfFile:path];
NSEnumerator *enumerator = [plistData objectEnumerator];
NSArray *personResults;
Photo *photo;
Person *person;
id currItem = [enumerator nextObject];
while (currItem != nil) {
photo = (Photo *)[NSEntityDescription insertNewObjectForEntityForName:#"Photo" inManagedObjectContext: [[FlickrFetcher sharedInstance] managedObjectContext]];
photo.name = [currItem objectForKey:#"name"];
photo.path = [currItem objectForKey:#"path"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name = %#", [currItem objectForKey:#"user"]];
personResults = [[FlickrFetcher sharedInstance] fetchManagedObjectsForEntity:#"Person" withPredicate:predicate];
if ([personResults count] > 0) {
person = [personResults objectAtIndex:0];
}
else {
person = (Person *)[NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:[[FlickrFetcher sharedInstance] managedObjectContext]];
person.name = [currItem objectForKey:#"user"];
}
photo.person = person;
[person addPhotosObject:photo];
NSLog(#"Photo %# added for user %#", photo.name, person.name);
currItem = [enumerator nextObject];
}
[plistData release];
}
}
And I call it in my apps didFinishLaunchingWithOptions method in my AppDelegate, the method is also in the same AppDelegate, when it's called I get the error it says there when debugging, if I don't debug the line it will run the method and load with no problem. If I don't debug at all it will not call the method.
EDIT: Changed the code according to the answer the problem still remains the same, if I just run nothing happens but when debugging I get the error. When I debug the whole method no error is shown.
Your call to NSLog is trying to access the name property of your person variable. However, when you declared your person variable, you didn't initialize it, so it points to garbage. You only give it a valid value in your else clause, so sometimes your NSLog is accessing an uninitialized object.