iPhone's Core Data crashes on fetch request - iphone-sdk-3.0

I'm using the following code to grab a few objects from SQLite store (which is a prepared SQLite db file, generated with Core Data on desktop):
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity: wordEntityDescription];
[request setPredicate: [NSPredicate predicateWithFormat: #"word = %#", searchText]];
NSError * error = [[NSError alloc] init];
NSArray * results = [[dao managedObjectContext] executeFetchRequest: request error: &error];
Eveyrthing seems to be setup properly, but executeFetchRequest:error: fails deeply inside Core Data (on NSSQLCore _newRowsForFetchPlan:selectedBy:withArgument) producing 256 error to the outside code.
The only kink I had setting up managedObjectContext is I had to specify NSIgnorePersistentStoreVersioningOption option to addPersistentStoreWithType as it was constantly producing 134100 error (and yes, I'm sure my models are just identical: I re-used the model from the project that produced the SQL file).
Any ideas?
P.S. Don't mind code style, it's just a scratch pad. And, of course, feel free to request any additional info. It would be really great if someone could help.
Update 1
Alex Reynolds, thanks for willingness to help :)
The code (hope that's what you wanted to see):
NSEntityDescription * wordEntityDescription; //that's the declaration (Captain Obviousity :)
wordEntityDescription = [NSEntityDescription entityForName: #"Word" inManagedObjectContext: ctx];
As for predicate – never mind. I was removing the predicate at all (to just grab all records) and this didn't make any differences.
Again, the same code works just fine in the desktop application, and that drives me crazy (of course, I would need to add some memory management stuff, but it at least should produce nearly the same behavior, shouldn't it?)

Can you add code to show how wordEntityDescription is defined?
Also, I think you want:
NSError *error = nil;
You may want to switch the equals symbol to like and use tick marks around the searchText field:
[request setPredicate: [NSPredicate predicateWithFormat: #"word like '%#'", searchText]];
NSPredicate objects are not put together like SQL, unfortunately. Check out Apple's NSPredicate programming guide for more info.

Related

Complex NSPredicate traversing multiple Entities and relationship types

I'm having a tough time solving this predicate issue since the database structure is a bit complex. I have the following database structure, or at least what is of concern for this question:
PUBLISHER<< --- >>PUBLICIST<<--BOOK<<--->AUTHOR<<-->>AGENTS
I tried the following predicate that I have used when I traversed relationships in the past, but not to this degree. I should mention that I have an NSArray with agent names that I want to query against the database to determine a list of publishing houses the agent works with:
NSArray *agentNames = [NSArray arrayWithObjects:Dan, Hunter, Sloan, Jackson];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"Publisher" inManagedObjectContext:context];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor
sortDescriptorWithKey:#"publisherName" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)] ];
request.predicate = [NSPredicate predicateWithFormat:#"ANY pubHouse.publicist.assignedBook.authorRep.agentName IN %#", [agentNames valueForKey:#"agentName"]];
request.fetchBatchSize = 20;
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
When I run the previous predicate I get the following warning:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'unimplemented SQL generation for predicate : ANY pubHouse.publicist.assignedBook.authorRep.agentName IN
I believe the predicate breaks when I travel the Book Entity relationship to the Author Entity and then onto the Agent Entity. At this point suggestions would help. Thanks
Your problem is that the predicate parser has no clue what set ANY should be applied to.
With this data model:
PUBLISHER<< --- >>PUBLICIST<<-->BOOK<<--->AUTHOR<<-->>AGENTS
… your keypath:
pubHouse.publicist.assignedBook.authorRep.agentName
… in terms of objects and set of the relationship looks something like:
object.set.object.object.set
So, that is two sets that the ANY could apply to.
You could try to build a subquery to handle the predicate but if you have to transverse that many relationships, your fetch will involve a big chunk of your data and will be very, very slow (assuming you get it work in the first place.)
Usually when you end up with a convoluted predicate like this it indicates that you are approaching the problem from the wrong end. In this case, it would be easier to start with a simple predicate like:
NSArray *agentNames = [NSArray arrayWithObjects:Dan, Hunter, Sloan, Jackson];
request.entity = [NSEntityDescription entityForName:#"Agent" inManagedObjectContext:context];
request.predicate = [NSPredicate predicateWithFormat:#"agentName IN %#", agentNames];
Then you would walk the relationship keypath of:
authors.books.publicist.publishers
… to find all the related publishers.
I think that you will have trouble no matter what you do because having more than one to-many-to-many relationship e.g.
PUBLISHER<<--->>PUBLICIST
… increases the complexity of predicates and relationship walks exponentially. Usually, in such a case, you may need an additional entity to more thoroughly model one of the relationships. That usually reduces the complexity of the data model itself which simplifies fetches and walks.
Perhaps NSArray has a problem with valueForKey:. I have seen a solution that uses NSCompoundPredicate adding the array items in a loop.
BTW, in your chain of multiple relationships, aren't you missing your authors?
Just wrap the IN clause of your predicate string in parentheses:
#"ANY (pubHouse.publicist.assignedBook.authorRep.agentName IN %#)"
Would be glad to know if it works.

Optimising CoreData fulltext query

I have a large collection of CoreData objects which represent files. Periodically, I need to search this collection and find files which exist at a given path. Currently, I'm constructing the following NSPredicate and performing a fairly basic executeFetchRequest to find all results which match my query.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"path ==[c] %#", receivedPath];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Files" inManagedObjectContext:self.moc];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setPredicate:predicate];
[request setFetchBatchSize:10];
NSError *error = nil;
NSArray *results = [self.moc executeFetchRequest:request error:&error];
[request release];
The issue I'm having is that this function is called fairly frequently and the executeFetchRequest is accounting for a significant percentage of execution time (Instruments tells me the function accounts for around 49.2% in total, with 98.1% of that total spent on the executeFetchRequest call). I'm using NSSQLiteStoreType for my persistent store and have the path attribute indexed in my MOM.
My question is, how can I optimise this? I've considered setting up a lowercasePath attribute, and ditching the [c] modifier on the comparison, but am unsure as to what impact (if any) that would have on execution time.
Any help would be greatly appreciated.
Removing the [c] should make a difference. ==[c] is probably translated to an SQLite LIKE operator for which SQLite cannot use indexes, if I remember correctly. Speaking of which, do you have the attribute marked as indexed in your model? If not, you should do so, but again, with a case-insensitive search, this probably doesn't make a difference.

passing JSON from ASIHTTPRequest to django

I've exhausted other threads, so I'm posting this question here. Please pardon any newbie mistakes I've made along the way. I've been reading a lot, and I think I'm getting confused.
The Goal:
I'm trying to pass data from a form in objective-c to my django web service. In an effort to assist with this, I've employed the ASIHTTPRequest class to facilitate information transfer. Once sent to the web service, I'd like to save that data to my sqlite3 database.
Procedure:
On the Objective-C side:
I've stored the inputted form data and their respective keys in an NSDictionary, like this:
NSDictionary *personInfo = [NSDictionary dictionaryWithObjectsAndKeys:firstName.text, #"fName", middleName.text, #"mName", lastName.text, #"lName", nil];
I've added it to my ASIHTTPRequest in a different class by using a delegate. I've made the NSDictionary the same as above in the code block below for simplicity, like so:
NSString *jsonPerson = [personInfo JSONRepresentation];
[request addRequestHeader: #"Content-Type" value:#"application/json; charset=utf-8"];
[request appendPostData:[jsonPerson dataUsingEncoding:NSUTF8StringEncoding]];
[request setRequestMethod:#"POST"];
[request startAsynchronous];
And a NSLog shows the string I'm passing to look like this, which validates at least in JSONLint
{"mName":"Arthur","lName":"Smith","fName":"Bob"}
Because I'm seeing what appears to be valid JSON coming from my ASIHTTPRequest, and actions are running from requestfinished: rather than requestfailed:, I'm making the assumption that the problem more than likely isn't on the Objective-C side, but rather on the django side.
Here's what I've tried so far:
json.loads(request.POST)
>>expected string or buffer
json.loads('request.POST')
>>no JSON object to decode
json.loads(request.raw_post_data)
>>mNamelNamefName
incoming = request.POST
>>{"mName":"Arthur","lName":"Smith","fName":"Bob"}
incoming = request.POST
onlyValues = incoming.iterlists()
>>(u'{"mName":"Arthur","lName":"Smith","fName":"Bob"}', [u''])
...and a smattering of other seemingly far-fetched variations. I've kept a log, and can elaborate. The only hope I've been able to find is in the last example; it looks like it's treating the entire string as the key, rather than breaking up each dict object and key as I would have expected.
I realize this is terribly elementary and I don't normally ask, but this problem has me particularly stumped. I do also remember reading somewhere that python won't recognize the double-quotes around each object and key, that to get it to something django likes, each should be surrounded by single-quotes. I just don't have any idea how to get them that way.
Thanks!
This might be a little cumbersome but you may try some simple regexp in objective c just to see if that is really the case
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"\"" options:NSRegularExpressionCaseInsensitive error:&error];
NSString *json = [regex stringByReplacingMatchesInString:jsonPerson options:0 range:NSMakeRange(0, [jsonPerson length]) withTemplate:#"'"];
There might be some errors because I didn't run the code.

-[NSCFNumber count]: unrecognized selector

I've got some Core Data code that follows Apple's sample code precisely (the Fetching Attribute Values that Satisfy a Given Function example). I'm using it to get the max value of a field, so I can then increment it when I insert the next object of that entity type.
I couldn't get the code to work at all, until I switched my Store Type from NSXMLStoreType to NSSQLiteStoreType, then all of a sudden everything seemed to be working. However, that's not the case. I noticed that it would always return the same value, even when I inserted objects with a higher one. But, after I quit and reopened (and thus the data was persisted and read back in), it would update with the new inserts.
So then I started committing and saving after each insert. After the first "autosave" though, I get the error below (twice in a row):
-[NSCFNumber count]: unrecognized selector sent to instance 0x100506a20
This occurs (two times in a rows) when I execute the fetch request once:
NSArray *objects = [context executeFetchRequest:request error:&error];
Update
I ran my code through the Zombies instrument, and was able to take a look at the object which is getting the error. The call that runs malloc to allocate it is: -[NSUserDefaults(NSUserDefaults) initWithUser:]. Since I don't have any of my own defaults set, I don't know what object this could be.
Update 2
I searched through all of my code for "release" and commented out every release or autorelease that the static analyzer didn't complain about. I still got the errors. I even went so far as to comment out every last release/autorelease in my code, and still got it. Now I'm fairly certain my own code isn't over-releasing.
Update 3
This post seems to be having the same problem, but his solution doesn't make sense. He changed the result type from NSDictionaryResultType to NSManagedObjectResultType, which produces an incorrect result. Instead of getting back a single value (the max that I'm looking for, that returns back every object of my entity class in the managed object context.
Here are the top-most levels of the stack trace (when I have it break on the exception, the first time):
#0 0x7fff802e00da in objc_exception_throw
#1 0x7fff837d6110 in -[NSObject(NSObject) doesNotRecognizeSelector:]
#2 0x7fff8374e91f in ___forwarding___
#3 0x7fff8374aa68 in __forwarding_prep_0___
#4 0x7fff801ef636 in +[_NSPredicateUtilities max:]
#5 0x7fff800d4a22 in -[NSFunctionExpression expressionValueWithObject:context:]
#6 0x7fff865f2e21 in -[NSMappedObjectStore executeFetchRequest:withContext:]
#7 0x7fff865f2580 in -[NSMappedObjectStore executeRequest:withContext:]
I've seen this question on numerous forums elsewhere on the web, but no one has offered a workable solution. By popular request, I added my own code below. To explain slightly, my entity's name is Box and the property I'm trying to get the value of is "sortOrder", an Int 32 attribute.
NSManagedObjectContext *context = [MyLibrary managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Box"
inManagedObjectContext:context]];
// Specify that the request should return dictionaries.
[request setResultType:NSDictionaryResultType];
// Create an expression for the key path.
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"sortOrder"];
// Create an expression to represent the function you want to apply
NSExpression *expression = [NSExpression expressionForFunction:#"max:"
arguments:[NSArray arrayWithObject:keyPathExpression]];
// Create an expression description using the minExpression and returning a date.
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
// The name is the key that will be used in the dictionary for the return value.
[expressionDescription setName:#"maxSort"];
[expressionDescription setExpression:expression];
[expressionDescription setExpressionResultType:NSInteger32AttributeType];
// Set the request's properties to fetch just the property represented by the expressions.
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
// Execute the fetch.
NSError *error;
NSNumber *requestedValue = nil;
NSArray *objects = [context executeFetchRequest:request error:&error];
NSLog( #"objects: %#", objects );
if (objects != nil && [objects count] > 0) {
requestedValue = [[objects objectAtIndex:0] valueForKey:#"maxSort"];
} else {
[[NSApplication sharedApplication] presentError:error];
}
[expressionDescription release];
[request release];
NSLog( #"Max Sort Order: %#", requestedValue );
return requestedValue;
Apparently this is a known bug, that occurs when using an NSInMemoryStoreType data store. It seems it works fine using an NSSQLiteStoreType.
You can find the OpenRadar entry here
I filled a duplicate for this bug — I encourage people that encounter the same issue to do the same, to increase the likelihood this annoying behaviour gets documented (or even better, fixed).
When you have memory management issues (selectors being sent to the wrong instances is a sign of memory management issues), there are a number of things you can do:
Re-read the Cocoa memory management rules and make sure that you're following them.
Run the static analyser. This will often pick up places where you have neglected the memory management rules.
Try using NSZombieEnabled to find out whether [and when] you are sending messages to unallocated instances.
-[NSCFNumber count]: unrecognized selector sent to instance 0x100506a20 means, that you are calling count on a NSCFNumber object, but NSCFNumber doesnt have this method. So most likely count is send to a deallocated NSArray or NSSet object.
USE NSZombieEnabled = YES. It might tell you, what happens. Search SO for informations on how to set it.
This can also happen if a binding is not set correctly. For example, if you bind a matrix boolean value to "Content" instead of (or in addition to) "Selected Tag" in IB you can get this error.
If all else fails, disconnect all of your bindings and reconnect them one at a time to see which one is the culprit.
After experiencing exactly the same problem with exactly the same sample code, it finally worked for me after I put [request release] in.
You are using the key path: sortOrder in your path expression. At least for XML-Databases Core-Data cannot handle case-sensitive types. Change your path to sortorder (all lower-case)
You will probably stumble over further problems if you are using controller classes.

Entity is not key value coding-compliant for the key

if (win) {
// Game was won, set completed in puzzle and time
// Calculate seconds taken
int timeTaken = (int)([NSDate timeIntervalSinceReferenceDate] - self.gameStartTime);
int bestTime = [[self.puzzle valueForKey:#"bestTime"] intValue];
if (timeTaken < bestTime && bestTime != 0) {
[self.puzzle setValue:[NSNumber numberWithInt:timeTaken] forKey:#"bestTime"];
NSLog(#"Best time for %# is %#", [self.puzzle valueForKey:#"name"], [self.puzzle valueForKey:#"bestTime"]);
}
}
This is some code from an iPad game I am making and I am using Core Data for storing the levels. When a level is completed and won, I want to set the best time for that level. The time taken is calculated, and if it is better than the previous best time, I want to set it as the best time for the level.
This code fails on the 'int bestTime' line when it tries to retrieve the best time from self.puzzle which is an NSManagedObject from Core Data. The best time is stored as an Integer 32 in the Core Data model. It fails with a SIGABRT error.
'[<NSManagedObject 0x95334d0> valueForUndefinedKey:]: the entity Puzzle is not key value coding-compliant for the key "bestTime".'
I have searched online for reasons as to why this is happening and how to fix it, but nothing seems to have helped. There are other places where I access Integer values from the Core Data model and they work perfectly, although they are used to filter and sort queries.
I also don't know if the line where I set the value will work.
Any help on this would be greatly appreciated.
EDIT: This is the code that fetches an array of puzzles of which one is taken to be the above puzzle.
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Puzzle" inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Set the filter for just the difficulty we want
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"difficulty == %d", difficulty];
[request setPredicate:predicate];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sortid" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
Ok, Firstly, I would like to thank everyone who suggested ideas. They may not have helped me solve the problem, but I learnt more about Core Data and it is always good to find out what I should be checking when things don't work.
I don't really know what the problem was. Until this morning I had Xcode open for about 5 days I think and yesterday I added the attribute 'bestTime' to the data model. I can only assume that over the 5 days, Xcode had become a little unstable and thought it was saved when it wasn't. I had checked that I had saved the model attributes, in fact I must have checked 3 or 4 times as well as my habit of hitting Command+S after any change I make.
Anyway, I rebooted my machine earlier today and when I started up Xcode a few minutes ago I realised that 'bestTime' was not in the model file. I added it, reset the settings on the iPad simulator and it worked.
Thank you all again for the help, sorry the solution wasn't more interesting and code based. Although it makes me feel better that my code wasn't the cause.
That managed object doesn't have an attribute named “bestTime”. According to the exception message, it definitely is a Puzzle, so you haven't declared an attribute named bestTime in your model (or you misspelled it or capitalized it differently).
I did solve the same problem by delete and create the data model again and clean then rebuild again.
I think the bug is caused by core data does not update some data inside sometimes.
I don't think there's enough information here to determine the cause. You might try reading the Core Data Troubleshooting Guide; one possible cause could be if you initialized this particular instance of Puzzle using plain init rather than initWithEntity.
If you added attribute bestTime to the model at the later time, you might have forgotten to put declaration and implementation for them in the connected Managed Object Class.
Try convenience actions provided in Design -> Data Model -> Copy Objective-C ... Method Declarations/Implementations to Clipboard (when editing your Model file).
If parsing JSON into a managed object, be sure you're using the coreDataPropertyName property rather than the json-key-name key from JSON. Easy to mix up when they're named so similarly.
This error was driving me nuts, and all because I was using image-url rather than imageURL.