NSPredicate with "NOT IN" condition fails - objective-c

Ok, here's my problem. I am synchronizing data from a server via a REST-api. The returned data is in JSON, I loop through it and takes appropriate actions depending on the data. That is, I either store it as a new object, updates the object if it already exists or deletes it if only exists locally.
To achieve this, I collect the IDs from the returned objects when I loop through the JSON. This gives me a index of all the returned objects. I then query my locally stored data to see if it contains any objects that should be deleted (in other words, if the local ID does exists or not in the JSON response).
And here's my issue (sorry for a somewhat lengthy prologue); the NSPredicate that I use only works for certain scenarios and which ones work or fails seems to be random.
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// Array which populates with the IDs from the server
NSMutableArray *arrayOfLogIDS = [[NSMutableArray alloc] init];
/*
Fetching and parsing JSON ... collecting IDs and adding them to the array. See example below;
*/
NSArray *logs = [[json valueForKey:#"Logs"] valueForKey:#"Object"];
// Looping through the logs array
for (NSArray *log in logs) {
[arrayOfLogIDS addObject:[log valueForKey:#"serverID"]];
}
// The NSPredicate
NSPredicate *serverIDS = [NSPredicate predicateWithFormat:#"NOT (serverID IN %#)", arrayOfLogIDS];
// The array which holds the objects that should be deleted
NSArray *logs = [Logs MR_findAllWithPredicate:serverIDS inContext:localContext];
}];
The problem is just that the NSPredicate won't work for this specific circumstance. It returns no results even though I know I have objects locally that should be deleted.
I use this approach in other places in the application, and it works as expected. As you can see I am using Magical Record for Core Data management in this app.
I feel that I have completely run out of things to try next, so any help would be much appreciated! :)

Ok, as it turns out, the array of IDs sometimes had the values stored as string and sometimes as integers. Integers worked well with NSPredicate, strings not so much :) Solved! Thanks all for your time.

Related

Retrieve Specific Object from Core Data

I'm new to Core Data and wondering if it is possible to get an object based on it's attributes, more specifically, an uniqueID I assigned to it. I'm trying to do this because I'm interfacing with a web server, which provides data that will updated the Core Data. I want to search through each of the web server objects, check the timestamp, and if it's different, retrieve that object from core data, and update. I've looked at using existingObjectWithId but it seems like I would have to know which object I'm searching for, or the ID of that object. I've also thought about sorting the data in both arrays, and then checking each simultaneously, but didn't think that is viable.
Here is what I'm doing so far:
-(NSMutableArray*) updateRootBeers:(NSMutableArray*)webRootBeerList{
NSManagedObjectContext* moc = [self managedObjectContext];
NSFetchRequest* fetchRequest = [[NSFetchRequest alloc]initWithEntityName:#"Rootbeer"];
coreDataRootBeerList = [[moc executeFetchRequest:fetchRequest error:nil]mutableCopy];
//check to see if core data has data, if not, call function to populate the list
if (coreDataRootBeerList.count <=0) {
[self addRootBeerToCoreData:webRootBeerList];
}else{
//otherwise I want to update the core data object if object data is different.
for(RootBeer* currentRootBeer in webRootBeerList){
RootBeer* mRootBeer = [moc existingObjectWithId:currentRootBeer error:nil];
}
}
}
I've also thought about using nested for loops to check for the data in each array, but that seems like poor coding.
Any help or thoughts would be great.
You want to make an NSFetchRequest. You can set the entity and provide a Predicate. Really simple and clean.

Objective-C: how to compare 2 PLists

I'm a total newbie to Objective-C and have been tasked with an assignment to compare 2 builds of same app for differences in their Info.plist and Defaults.plist.
I have been able to figure out the steps to read the PLists from app bundle but am having difficulty figuring out how to compare EVERY key in PLists to its counterpart file. For illustration if I need to compare Info.plist between 2 app bundle (lets say build_100 and build_101), how do I recursively go to each key in build_100 and compare the same key in build_101 to verify if they are same or not.
Its easy if both PLists are same because isEqualToDictionary will return TRUE but problem occurs if something in a nested dictionary is different between both the builds.
Going through related queries here, it clear to me that the answer is that I write a recursive method that iterates through both PLists but I'm having a real frustrating time to figure out a way to do this for a nested dictionary like Info.plist.
So I've finally figured this thing out so thought of sharing it with others for future reference. I'm sure there'll be some other lost soul in future looking for something similar (or at least I hope :)).
The way I wrote my code was to:
Read both Plists in NSDictionaries
Treat one Plist as "to be tested" and other as the reference (to compare against) to find out if its a Pass/Fail
Loop through all keys in "to be tested" Plist and compare each one of them in "reference" Plist
When it came to compare an Array or Dictionary, this check (that's the part I was struggling with) had to be a recursive check
The code to write for step #1, 2, 3 is straight forward so I'm going to give the method I wrote for #4 which was the crux of my original question.
This function compareSourceObject() will take 3 arguments:
sourceObject: object to be tested
targetObject: object to compare against
trailPath: string that'll hold the entire path of the key that has failed
- (void)compareSourceObject:(id)sourceObject andTargetObject:(id)targetObject withBreadcrumbTrail:(NSString *)trailPath{
NSString *message = [[NSString alloc] init];
if ([sourceObject isKindOfClass:[NSDictionary class]]){
for(id item in sourceObject){
[self compareSourceObject:[sourceObject objectForKey:item] andTargetObject:[targetObject objectForKey:item] withBreadcrumbTrail:[trailPath stringByAppendingFormat:#"->%#", item]];
}
}
else if ([sourceObject isKindOfClass:[NSArray class]]){
for (int counter=0; counter %d", counter]];
}
}
else if(![sourceObject isEqual:targetObject]){
NSLog(#"Values do not match. Value in \"TestedDicationary\" is (%#) but the reference dict has (%#)", targetObject, sourceObject);
}
}
Hope this helps. Comments/Suggestions/Optimizations are more than welcome.
Take one plist, and interpret the properties as a set (NSSet) of string values, e.g.
:items:0:assets array
:items:0:assets:0:kind string VALUE
Note I am using /usr/libexec/PlistBuddy format to describe a property - path type [value].
Then do the same for the second plist and compare the sets using NSSet functions.

How to use the "ALL" aggregate operation in a NSPredicate to filter a CoreData-based collection

Based on the data model below
And based on user input I create a NSSet of managedObjects of entity Tag called selectedTags.
My problem:
[NSPredicate predicateWithFormat:#"ANY entryTags IN %#", selectedTags];
... this will return any Entry with at least one entryTag that is in the selectedTags set.
I want something along the lines of:
[NSPredicate predicateWithFormat:#"ALL entryTags IN %#", selectedTags];
... notice the only change is the "ANY" to "ALL". This illustrates what I want, but does not work.
To formulate the outcome I expect:
I'm looking for a solution that will return only Entries who's entryTags are all in the selectedTags list (but at the same time, if possible, not necessarily the other way around).
To further illustrate:
(tag)Mom
(tag)Dad
(tag)Gifts
(entry)she is a she.....(tag)mom
(entry)he is a he........(tag)dad
(entry)gifts for mom...(tags:)mom, gifts
(entry)gifts for dad.....(tags:)dad, gifts
If selectedTags contains "mom" and "gifts", then the entry "gifts for dad" will show up, since it has the tag "gifts". I'd rather have it not show :)
This is the definite answer so far:
[NSPredicate predicateWithFormat:#"SUBQUERY(entryTags, $tag, $tag IN %#).#count = %d", selectedTags, [selectedTags count]];
B-E-A-U-T-I-F-U-L.
Thanks to Dave DeLong.
How about using a compound predicate? As I understand you want to return all Entries that match a list of tags not just any of them. One approach would be to create a predicate for each tag, then AND them together using a compound predicate.
NSMutableArray *predicates = [[NSMutableArray alloc] init];
for (Tag *tag in selectedTags) {
[predicates addObject:[NSPredicate predicateWithFormat:#"ANY entryTags.tagName MATCHES %#", tag.tagName]];
}
NSPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
This should achieve want you want. Then just set this predicate on your request.
You can't do what you want with a predicate.
The ANY and ALL operators apply to the entity being tested (in this case Entry) and not the contents of the collection (selectedTags). Either operator will return an Entry object that matches any single element of the collection. The ANY operator will return the first match it finds while the ALL operator will return all matches. In neither case will they return an entry that matches every element in the provided collection.
(It also looks like you are trying to use actual Tag objects in selectedTags. That will most likely not work either because object compares on classes without dedicated comparison methods usually fail. It is also slow. You need to compare attributes in predicates.)
Since you already have the Tag objects you want, to find the candidate related Entity objects, you just have to walk the Tag.taggedEntries relationship. Then you have to find the intersection of all the sets of Entity object to find only those Entity objects that are related to every selected Tag bject. Since there isn't an intersect collections operator, you need a loop.
if ([selectedEntries count]>=2) {
NSMutableSet *intersectEntries=[[NSMutableSet alloc] initWithCapacity:1];
for (int i=1; i<[selectedTags count]; i++) {
if ([intersectEntries count]==0) {
[intersectEntries unionSet:[[selectedEntries objectAtIndex:(i-1)] valueForKey:#"taggedEntries"]];
}
[intersectEntries intersectSet:[[selectedEntries objectAtIndex:i] valueForKey:#"taggedEntries"]];
}
}
(Note: I didn't test this but it should work.)
Now intersectEntries should contain only those Entry objects that are related to every selected tag.
I realized I could give something back here for the guidance that I have previously gotten. By using the code TechZen supplied I was able to come up with the following -- and for me highly valued -- piece of code:
- (NSArray *)unionSetOfObjectsForObjects:(NSArray *)objects {
NSMutableSet *unionSetOfObjects = [NSMutableSet set];
if (objects.count)
[unionSetOfObjects unionSet:[[objects objectAtIndex:0] valueForKey:#"key"]];
//unionSetOfObjects = [[objects objectAtIndex:0] valueForKey:#"key"];
if (objects.count > 1)
for (id object in objects)
[unionSetOfObjects intersectSet:[object valueForKey:#"key"]];
return unionSetOfObjects.allObjects;
}
If it is not immediately obvious what this code does:
It collects all the values (in my case objects) for the key key on all of the objects provided in the objects array.
This code just... tastes good, doesn't it?
The simplest way to do this:
[NSPredicate predicateWithFormat:#"entryTags IN %#", selectedTags];
You don't need the ALL clause. It's also documented here:
Predicate Programming guide
And as you can see in this post the user does it successfully (read the comments to the original question)
NSPredicate iPhone 3.2 SDK Core Data “IN clause”...

NSFetchRequest and predicateWithBlock

I am playing with an app that uses Core Data and NSManagedObjects to populate a UITableView. There is only one class in my application, called Event. I have created the following custom instance method on Event:
- (BOOL)isExpired {
return ([[self.endOn dateAtEndOfDay] timeIntervalSinceNow] < 0);
}
I would like to limit the UITableView that displays Event objects to only the Events that are expired - that is, where isExpired returns YES. I have tried to do this by adding an NSPredicate to the NSFetchRequest:
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary * bindings) {return([evaluatedObject isExpired]);}];
[fetchRequest setPredicate:predicate];
but I get the error: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Problem with subpredicate BLOCKPREDICATE(0x272ac)'
***. Does this mean that you can't use a block predicate with an NSFetchRequest? Or have I just constructed it improperly?
Thank you!
So, it appears that we've established in the comments to the original post that this is likely caused by SQLite stores being incompatible with block predicates, since Core Data cannot translate these to SQL to run them in the store (thanks, JoostK).
There might be a couple of ways to overcome this:
Provided that the end date of your entities is a regular attribute, you might be able to express the expiry constraint as a predicate format string instead of a block predicate, which Core Data should be able to translate into a SQL clause.
If the above is possible, you will probably prefer to use a fetch request template to retrieve the expired items. You would need to pass in a substitution variable like $NOW to give access to the current date, though. This has the advantage of making the predicate template show up in the model editor.
Both approaches, however, have the disadvantage of duplicating existing functionality (i.e., your isExpired method). So another way would be fetch all qualifiying entities regardless of their expiry state first, and then run a dedicated filtering step on the resulting set of entities to weed out the non-expired ones. Since by that point, they have been fully resurrected from the store, you should be able to use a block predicate for this.
You can do a normal fetch request without specifying the predicate, and afterwards filter the resulting array:
NSArray *allEvents = [context executeFetchRequest:fetchRequest];
if (!allEvents) { // do error handling here
}
NSArray *expiredEvents = [allEvents filteredArrayUsingPredicate:predicate];

traverse Core Data object graph with added predicate?

I want to load a client object and then pull their related purchase orders based on whether they have been placed or not, purchase orders have an IsPlaced BOOL property.
So I have my client object and I can get all purchase orders like this, which is working great:
purchaseordersList =[[myclient.purchaseorders allObjects] mutableCopy];
But ideally I would actually like 2 array's - one for each order type: IsPlaced=YES and IsPlaced=NO
How do I do that here? Or do I need to do another fetch?
First, there is no reason to be turning the set into an array unless you are sorting it and there is no reason to be turning that array into a mutable array. Did you get that from some example code?
Second, you can filter an array or a set by using a predicate so you can create two sets (or arrays) easily via:
NSSet *placed = [[myclient purchaseorders] filteredSetUsingPredicate:[NSPredicate predicateWithFormat:#"isPlaced == YES"]];
NSSet *notPlaced = [[myclient purchaseorders] filteredSetUsingPredicate:[NSPredicate predicateWithFormat:#"isPlaced == YES"]];
If you are wanting to use this for a UITableView then look into a NSFetchedResultsController instead. It will save you a LOT of boiler-plate code.
Do you remember what example code you got that from? Been seeing that -mutableCopy a lot lately and would love to quash it. :)