How to serialize a NSPredicate object? - core-data

Is there a way to inspect a NSPredicate object for the purposes of serializing it into a URL? I am trying to retrieve the data remotely and need to translate the predicate object into a URL with querystring parameters that the server understands.
This was inspired by a talk given in WWDC 2010 called "Building a Server Driven User EXperience" where the speakers talk about using Core-Data and with a server backend. I have followed the session video and slides, but am stuck on the serializing point. For example, there is a Person object, and I'm trying to fetch all people whose first name is "John". I am using a subclass of NSManagedObjectContext called RemoteManagedObjectContext, which overrides the executeFetchRequest method, and is supposed to send the call to the server instead. The fetch request is being created as (ellipsed non-essential parts):
#implementation PeopleViewController
- (NSArray *)getPeople {
RemoteFetchRequest *fetchRequest = [[RemoteFetchRequest alloc] init];
NSEntityDescription *entity = ...
NSPredicate *template = [NSPredicate predicateWithFormat:
#"name == $NAME AND endpoint = $ENDPOINT"];
NSPredicate *predicate = [template predicateWithSubstitutionVariables:...];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
// the custom subclass of NSManagedObjectContext executes this
return [remoteMOC executeFetchRequest:fetchRequest error:&error];
}
#end
Now inside the custom subclass of NSManagedObjectContext, how can I serialize the fetch request into querystring parameters suitable for the server. So given the above fetch request, the corresponding URL would be:
http://example.com/people?name=John
It is possible to get a string representation of the predicate which returns,
name == "John" AND endpoint == "people"
that I can parse to get the parameters name, and endpoint. However, is it possible to do it without parsing the string? Here's a partial implementation of the RemoteManagedObjectContext class.
#implementation RemoteManagedObjectContext
- (NSArray *)executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error {
// this gives name == "John" AND endpoint == "people"
// don't know how else to retrieve the predicate data
NSLog(#"%#", [[request predicate] predicateFormat]);
...
}
#end

Even better than a string representation is an object-oriented representation! And it's done automatically!
First, check the class of the NSPredicate. It will be an NSCompoundPredicate. Cast it to an appropriate variable.
You'll then see that it's compoundPredicateType is NSAndPredicateType, just like you'd expect.
You can also see that the array returned by -subpredicates reveals 2 NSComparisonPredicates.
The first subpredicate has a left expression of type NSKeyPathExpressionType and a -keyPath of #"name", the operator is NSEqualToPredicateOperatorType. The right expression will be an NSExpression of type NSConstantValueExpressionType, and the -constantValue will be #"John".
The second subpredicate will be similar, except that the left expression's keyPath will be #"endpoint", and the right expression's constantValue will be #"people".
If you want more in-depth information on turning NSPredicates into an HTTP Get request, check out my StackOverflow framework, "StackKit", which does just that. It's basically a framework that behaves similarly to CoreData, but uses StackOverflow.com (or any other stack exchange site) to retrieve information. Underneath, it's doing a lot to convert NSPredicate objects into a URL. You're also welcome to email me any specific questions you have.

Related

CLGeocoder not returning neighborhood name when I reversegeocode

I am currently working on an app that will use the data returned by reversegeocode. Right now I can successfully receive the following values for a location: address, city, state, zip code, and country. In addition to the values that I am able to get, I would also like to obtain the name of the neighborhood for the locations that I reversegeocode. My code is as follows:
CLLocationManager *location = [[CLLocation alloc] init];
[location setDelegate:self];
location.desiredAccuracy=kCLLocationAccuracyBest;
location.distanceFilter=kCLDistanceFilterNone;
[location startMonitoringSignificantLocationChanges];
CLGeocoder *geolocation = [[CLGeocoder alloc] init];
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
NSLog(#"Update method is definitely being called!");
NSLog(#"Your current location is : %#", [locations lastObject]);
[geolocation reverseGeocodeLocation:[locations lastObject] completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"Reverse geocode complete: %#", placemarks);
CLPlacemark *placemark = [placemarks objectAtIndex:0];
NSLog(#"The locality area is: %#", placemark.locality);
}];
}
I expected placemark.locality to return the neighborhood but it returns the city instead.
Any help would be greatly appreciated,
Dave
After reading the documentation Apple has for the CLPlacemark class, I noticed that there were a few fields that I was unaware of.
Inclusive in these fields is exactly what I was trying acquire, the subLocality, which seems to be Apple documentation for neighborhood. If I had just read the documentation instead of assuming the object returned from [placemarks objectAtIndex:0], when stored in CLPlacemark *placemark, would have no more data than what is shown when NSLog(#"%#", [placemarks objectAtIndex:0]) is called, I would have figured this out much sooner. Oh well. The code I used to access the neighborhood is:
[placemark subLocality];

How to sort Core Data results based on an attribute of related object collection?

Setup: I have a collection of parent objects, call them ObjectA. Each ObjectA has a one-to-many relation to ObjectB. So, one ObjectA may contain 0..n ObjectB-s, and each ObjectB has a specific ObjectA as its parent.
Now, I would like to do a Core Data fetch of ObjectA-s, where they are sorted by their latest ObjectB. Is it possible to create a sort descriptor for that?
There is a related question that describes exactly the same situation. The answer suggests denormalizing the attribute from ObjectB into ObjectA. This would be OK if there really is no way to do this with one fetch request.
The related question also mentions:
Actually, I just had an idea! Maybe I can sort Conversations by messages.#max.sortedDate…
I tried. It doesn’t seem to be possible. I get this error:
2012-10-05 17:51:42.813 xxx[6398:c07] *** Terminating app due to uncaught
exception 'NSInvalidArgumentException', reason: 'Keypath containing
KVC aggregate where there shouldn't be one; failed to handle
ObjectB.#max.creationTime'
Is denormalizing the attribute into ObjectA the only/best solution?
You could add an attribute in ObjectB which is the time stamp of the add date, then in the fetch request you can do something like this:
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"objectB.addTime" ascending:YES];
...
fetchRequest.sortDescriptors = #[descriptor];
I know this question is a bit old but what I did was get all ObjectBs, iterate over the results and pull out the ObjectB property and add it to a new array.
NSFetchRequest *fetchRequest = [NSFetchRequest new];
[fetchRequest setEntity:self.entityDescForObjectB];
// sort
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
NSError *error = nil;
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"Error fetching objects: %#", error.localizedDescription);
return;
}
// pull out all the ObjectA objects
NSMutableArray *tmp = [#[] mutableCopy];
for (ObjectB *obj in fetchedObjects) {
if ([tmp containsObject:obj.objectA]) {
continue;
}
[tmp addObject:obj.objectA];
}
This works because CoreData is an object graph so you can work backwards. The loop at the end basically checks to see if the tmp array already has a specific ObjectA instance and if not adds it to the array.
It's important that you sort the ObjectBs otherwise this exercise is pointless.

Count entity in Core Data with a specific Value

I have an Entity with some Attribute. I have my tabes already populates(SQLite table)
In one Attribute (i'll call Attribute1) i have a bool value, changing during use of my app.
How can i return the count of my Entities with Attribute1 value YES?
I've already read "Core data Tutorial" and "Predicate Programing Guide" but i don't understand how to proceed..
NSPredicate *predicate= [NSPredicate predicateWithFormat:#"Attribute1 == %#",[NSNumber numberWithBool:YES]];
I've tried with this, and then? it seems not working..
The best bet is to use the countForFetchRequest method. Set up your predicate and fetch request, but instead of doing the actual fetch, execute countForFetchRequest as follows:
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSPredicate *predicate =
[NSPredicate predicateWithFormat:#"Attribute1 == %#",
[NSNumber numberWithBool:YES]];
[request setPredicate:predicate];
NSUInteger count = [myManagedObjectContext countForFetchRequest:request error:nil];
You can find more info in the Apple API Docs:
countForFetchRequest:error:
Returns the number of objects a given fetch request would have returned if it had been passed to executeFetchRequest:error:.
(NSUInteger)countForFetchRequest:(NSFetchRequest )request error:(NSError *)error
Parameters
request
A fetch request that specifies the search criteria for the fetch.
error
If there is a problem executing the fetch, upon return contains an instance of NSError that describes the problem.
Return Value
The number of objects a given fetch request would have returned if it had been passed to executeFetchRequest:error:, or NSNotFound if an error occurs.
Availability
Available in iOS 3.0 and later.
Declared In
NSManagedObjectContext.h

Setting up basic relationship with Fetch Requests

I am wanting to set up a basic relationship with two entities in Core Data, but the relationship is either not saving, or is not working properly and I'm not sure why.
The two entities are Character and Avatar, its a one-to-one relationship. A character can have 1 avatar. Technically, it should be a "one avatar can be owned by many characters", but I'll deal with that later.
I want to add characters and assign them an avatar.
There are already 10 avatars in Core Data and 1 character, both of which I've verified via the Terminal and SQLite.
The problem is, I'm having troubling "finding an avatar by a name and then saving the relationship to a character".
So far,
I set up a fetch request called: "frqAvatarWithName" where the Predicate has the following structure:
[quote]
name == $AVATAR_NAME
[/quote]
This is so: I can find an avatar with a certain name; and then I can create a relationship with a character.
Issue 1: It gets to execute the query but then never displays how many records there are.
I get a EXC_BAD_ACCESS error in debug mode and I have traced it back to the fetch request template handling -- so, this must be in error or I have done it wrong.
Issue 2: I am not sure if I am even setting up this "basic" relationship up properly.
[code]
// This code is meant to find an avatar with a certain name and then save the relationship
// between a character and said avatar.
// This is my app delegate file for the moment
// All the files are present, and I have deleted/recreated the app various times
-(void)characterMaker
{
NSLog(#"Inside characterMaker...");
NSError *error = nil;
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObjectModel *model = [self managedObjectModel];
// Find an avatar with a specific name
NSString *nameToFind = #"avt_player_1";
// Use a Fetch request template
NSDictionary *subs = [NSDictionary dictionaryWithObjectsAndKeys:nameToFind, #"AVATAR_NAME", nil];
NSFetchRequest *fetchRequest = [model fetchRequestFromTemplateWithName:#"frqAvatarWithName"
substitutionVariables:subs];
// Set the entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Avatar"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Execute the query (it never even reaches this point)
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
// Handle the error
NSLog(#"Error -- %#", [error localizedDescription]);
abort();
}
NSLog(#"Found %# records", [fetchedObjects count]);
// Print out avatar names
for (Avatar *a in fetchedObjects)
{
NSLog(#"Name = %#", [a valueForKey:#"name"]);
}
// This is where I would use `a` and store it in a character entity, and thus create the relationship
[/code]
I gave up on this and did the whole project with the FMDatabase project and SQLite; I've been able to resolve the problem this way.
Thread closed.

Using GameKit to transfer CoreData data between iPhones, via NSDictionary

I have an application where I would like to exchange information, managed via Core Data, between two iPhones.
First turning the Core Data object to an NSDictionary (something very simple that gets turned into NSData to be transferred).
My CoreData has 3 string attributes, 2 image attributes that are transformables.
I have looked through the NSDictionary API but have not had any luck with it, creating or adding the CoreData information to it.
Any help or sample code regarding this would be greatly appreciated.
I would recommend that you convert the Core Data objects to an intermediate format like JSON before pushing it over the wire. I have written up the code on how to transform NSManagedObject instances into and out of JSON in this other post:
JSON and Core Data on the iPhone
NSManagedObject doesn't conform to the NSCoding Protocol so you can't convert a managed object straight to data.
Instead, you just need to add a method to the managed object subclass that returns a dictionary with the instance attributes and then on the receiver side, use those to create a new managed object in the local context.
Edit:
From comments:
Currently I have for the sending
side..
NSData* data;
NSString *str0 = [NSString stringWithFormat:#"%#",[[person valueForKey:#"PersonName"] description]];
NSString *str1 = [NSString stringWithFormat:#"%#",[[person valueForKey:#"alias"] description]];
NSMutableDictionary *taskPrototype = [NSMutableDictionary dictionary];
[taskPrototype setObject:str0 forKey:#"PersonName"];
[taskPrototype setObject:str1 forKey:#"alias"];
data = ?????;
//I do not know what to put here... [self mySendDataToPeers:data];
on the receiving side I have...
NSMutableDictionary *trial = [[NSMutableDictionary alloc] initWithData:data];
NSString *str0a = ???? NSString *str1a = ????
//I dont know what to put after this to retrieve the values and keys from the dictionary
You would simply reverse the process to create a managed object on the receiver.
NSMutableDictionary *trial = [[NSMutableDictionary alloc] initWithData:data];
NSManagedObject *person=[NSEntityDescription insertNewObjectForEntityForName:#"PersonEntity" inManagedObjectContext:moc];
[person setValue:[trial objectForKey:#"PersonName"] forKey:#"PersonName"];
[person setValue:[trial objectForKey:#"alias"] forKey:#"alias"];
.. and you're done.

Resources