NSPredicate aggregate IN statement not working - iphone

I am a bit of a CoreData noob but am slowly getting my head around it. I am having trouble with the following code:
NSArray * artisteIds = [#"1,2,3,4" componentsSeparatedByString:#","];
predicate = [NSPredicate predicateWithFormat:#"(artisteId IN %#)", artisteIds];
My Artiste managed object has a NSNumber field of artisteId and I have looped through all Artistes in my object context and there are definately objects with id's 1-420.
But my helper method always returns a empty result set with that query
NSMutableArray* mutableFetchArtistes = [CoreDataHelper searchObjectsInContext:#"Artiste" :predicate :#"title" :YES :managedObjectContext];
Any suggestions?
To test it works I used the following and got a count of 3 results
predicate = [NSPredicate predicateWithFormat:#"(artisteId = 1) or (artisteId = 2) or (artisteId = 3)", artisteIds];

I think the array artisteIds should contain NSNumber objects, e.g.
NSArray *artisteIds = [NSArray arrayWithObjects:
[NSNumber numberWithInteger:1],
[NSNumber numberWithInteger:2],
[NSNumber numberWithInteger:3],
[NSNumber numberWithInteger:4],
nil];
You could also use NSSet rather than NSArray, but I'm not sure if it makes a difference.

Related

Sort an NSMutableArray / Dictionary with several objects

I am having a problem that I think I am overcomplicating.
I need to make either an NSMutableArray or NSMutableDictionary. I am going to be adding at least two objects like below:
NSMutableArray *results = [[NSMutableArray alloc] init];
[results addObject: [[NSMutableArray alloc] initWithObjects: [NSNumber numberWithInteger:myValue01], #"valueLabel01", nil]];
This gives me the array I need but after all the objects are added I need to be able to sort the array by the first column (the integers - myValues). I know how to sort when there is a key, but I am not sure how to add a key or if there is another way to sort the array.
I may be adding more objects to the array later on.
Quick reference to another great answer for this question:
How to sort NSMutableArray using sortedArrayUsingDescriptors?
NSSortDescriptors can be your best friend in these situations :)
What you have done here is create a list with two elements: [NSNumber numberWithInteger:myValue01] and #"valueLabel01". It seems to me that you wanted to keep records, each with a number and a string? You should first make a class that will contain the number and the string, and then think about sorting.
Doesn't the sortedArrayUsingComparator: method work for you? Something like:
- (NSArray *)sortedArray {
return [results sortedArrayUsingComparator:(NSComparator)^(id obj1, id obj2)
{
NSNumber *number1 = [obj1 objectAtIndex:0];
NSNumber *number2 = [obj2 objectAtIndex:0];
return [number1 compare:number2]; }];
}

How to sort an NSArray full of EKCalendars by the title

I've got a NSArray with a bunch of EKCalendar Objects in it. I need to sort them alphabetically. I'm new to selectors but I think I need something like...
NSArray *array = [otherArray sortedArrayUsingSelector:#selector('localizedCaseInsensitiveCompare:title')];
Cheers
You cannot do it that way. Instead do the following:
NSArray *sortedArray = [array sortedArrayUsingComparator: ^(id obj1, id obj2) {
EKCalendar *cal1 = (EKCalendar *)obj1;
EKCalendar *cal2 = (EKCalendar *)obj2;
return [cal1.title localizedCaseInsensitiveCompare:cal2.title];
}];
Edit - an explanation:
-sortedArrayUsingComparator takes what is called a 'block' (an inline function) that must return an NSComparisonResult. All the hard work is done for you, as your block is run for as many pairs of objects as is needed to establish the correct order. Then all this does is cast each object type to an EKCalendar and then compare the two titles. You can adapt this to work for any type of object.
This should do the trick:
NSMutableArray *sortDescriptors = [NSMutableArray array];
NSSortDescriptor *sortByTitleAsc = [[[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES] autorelease];
[sortDescriptors addObject:sortByTitleAsc];
NSArray *arraySortedByTitle = [otherArray sortedArrayUsingDescriptors:sortDescriptors];
No, you don't want to use a selector, you want to use a key path, which requires a sort descriptor. You can't append an arbitrary property name to a selector name. The selector must exactly match the method name. Otherwise you just get nothing (nil/NULL/0) for the selector.
id sortedArray = [array sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES]]];
For the sake of completeness and and timeliness, here’s the Swift 4 version:
let store = EKEventStore()
let calendars = store.calendars(for: .event)
let calendarsSorted = calendars.sorted { $0.title.localizedCaseInsensitiveCompare($1.title) == ComparisonResult.orderedAscending }
By the way, please don’t forget to request access to the store by calling store.requestAccess(to:completion:) before accessing the store’s data.

Array of objects from array passing test

I have an NSArray of objects, which have a property id.
I then have another NSArray with a selection of ids.
I need to get all the objects in the first array which have the ids listed in the second array.
Is it possible to do this without for loops (well 1 for loop is ok, but I'd like to avoid it). I know how to do this with 2 for loops, but this seems very inefficient. So basically I'm looking for the most efficient way.
(The Id is an NSURL btw, so it can't be anything integer specific)
No loops!
NSArray *arrayOfIdentifiers = ...;
NSArray *arrayOfObjects = ...;
NSPredicate *filter = [NSPredicate predicateWithFormat:#"id IN %#", arrayOfIdentifier];
NSArray *filteredObjects = [arrayOfObjects filteredArrayUsingPredicate:filter];
Well, no loops that you write. There are probably loops inside filteredArrayUsingPredicate:.
You need an intersection os sets.
NSMutableSet *set1=[[[NSMutableSet alloc] initWithArray:array1] autorelease];
NSMutableSet *set2=[[NSMutableSet alloc] initWithArray:array2];
[set1 intersectSet:set2];
[set2 release];
NSArray *newArray=[set1 allObjects];

Core Data performance issues

I have an iPhone app that has the needs to avoid inserting duplicate records, at first I thought I could just test each object in my in-memory array, create a fetch request for each object and test if it exists. But this is proving slow on the device, very slow (about 5 seconds, which is not acceptable).
I have been trying to piece together how to create some smart predicate that I could use in order to get this to work efficiently but without much success.
My objects have a NSNumber field that I have set as the "Identity Property" and also non-optional. This field is called sampleTime (again, this is NOT a date, but a NSNumber)
Here is my idea (borrowed from other threads and even some of my own questions of SO):
Obviously doing a fetch per object (around 380 objects) is not going to work for performance, so I was under the impression that I could do most of it in memory and it would be faster. I need to create some predicate that uses the IN clause, then iterate over that fetch result, testing if any one of the objects is inside that result set, if NOT then insert it, if SO then do nothing.
But my implementation is not working:
NSMutableArray *timeStampArray = [[NSMutableArray alloc] init];
for (id emTmp in myListOfObjects)
[timeStampArray addObject: [NSNumber numberWithInt:[[emTmp sampleTime] timeIntervalSince1970]]];
NSFetchRequest *fetch = [[[NSFetchRequest alloc] init] autorelease];
[fetch setEntity:[NSEntityDescription entityForName:#"ElectricalMeasurementEntity" inManagedObjectContext:context]];
[fetch setPredicate:[NSPredicate predicateWithFormat:#"sampleTime in %#", timeStampArray]];
NSArray *results = [context executeFetchRequest:fetch error:nil];
int resCount = [results count];
But resCount is always zero, and I dont know why...
BTW, myListOfObjects contains business objects which also have a sampleTime property which IS an NSDate type.
EDIT
Ok, update, I got the basics working. The reason why I was not getting any results was because the loop that created the array was using the id type, which when used the way I was using it was not creating NSNumber objects correctly.
Now I do this:
for (id emTmp in myListOfObjects)
{
Measurement *t = (Measurement*)emTmp;
NSTimeInterval d = [t.sampleTime timeIntervalSince1970];
[timeStampArray addObject: [NSNumber numberWithInt:[[t sampleTime] timeIntervalSince1970]]];
}
which creates a nice list which works very well.
However, I then go on to do this:
NSMutableArray *itemsToInsert = [[NSMutableArray alloc] init];
for (id em in myListOfObjects)
{
BOOL found = NO;
Measurement *t = (Measurement*)em;
for (id e in results) //results from Fetch Request which is now populated properly
{
MeasurementEntity *entity = (MeasurementEntity*)e;
if ([entity.sampleTime isEqualToNumber:[NSNumber numberWithInt:[[t sampleTime] timeIntervalSince1970]]])
{
found = YES;
break;
}
}
if (!found)
[itemsToInsert addObject: t];
}
This loop (for around 850 objects, on the iPhone 3Gs) takes around 10 - 12 seconds, which I can see why (when 850*850 = 722500 loops!). Can I be more efficient about this?
Thanks
You need to strip the fetch down such that it will only check the one property. Then do all your comparisons with predicates for speed. Something like this:
NSArray *newData=[NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:6],nil];
NSManagedObject *mo;
for (int i=0; i<5; i++) {
mo=[NSEntityDescription insertNewObjectForEntityForName:#"Test" inManagedObjectContext:self.moc];
[mo setValue:[NSNumber numberWithInt:i] forKey:#"numAttrib" ];
}
[self saveContext];
NSFetchRequest *fetch=[[NSFetchRequest alloc] init];
NSEntityDescription *testEntity=[NSEntityDescription entityForName:#"Test" inManagedObjectContext:self.moc];
[fetch setEntity:testEntity];
// fetch only the one property you need to test
NSDictionary *propDict=[testEntity propertiesByName];
[fetch setPropertiesToFetch:[NSArray arrayWithObject:[propDict valueForKey:#"numAttrib"]]];
// Return as dictionaries so you don't have the overhead of live objects
[fetch setResultType:NSDictionaryResultType];
// fetch only those existing property values that match the new data
NSPredicate *p=[NSPredicate predicateWithFormat:#"numAttrib in %#",newData];
[fetch setPredicate:p];
NSArray *fetchReturn=[self performFetch:fetch];//<-- my custom boilerplate
// extract the existing values from the dictionaries into an array
NSArray *values=[fetchReturn valueForKey:#"numAttrib"];
// filter out all new data values that already exist in Core Data
p=[NSPredicate predicateWithFormat:#"NOT (SELF in %#)",values];
NSArray *unmatchedValues=[newData filteredArrayUsingPredicate:p];
NSLog(#"unmatcheValues=%#",unmatchedValues);
... which outputs:
unmatcheValues=(
6
)
Now you only need to create new managed objects for the values returned. All other new values already exist.
This may seem an obvious suggestion, but if [context executeFetchRequest:fetch error:nil] is not returning any results, seems like the first thing to do is check for errors instead of ignoring them (by setting error:nil).
Something like:
NSError *error = nil;
NSArray *results = [context executeFetchRequest:fetch error:&error];
if (results == nil) { // fetch failed - huh?
NSLog(#"Fetch error %#, %#", error, [error userInfo]);
}

Does CoreData on iPhone support IN predicates?

I'm trying to fetch a bunch of records of a certain type, based on a list of types defined by the user…
[fetchRequest setEntity:[NSEntityDescription entityForName:#"myRecord" inManagedObjectContext:self.managedObjectContext]];
NSSet *shipTypes = [NSSet setWithObjects:[NSNumber numberWithInt:70],
[NSNumber numberWithInt:71],
[NSNumber numberWithInt:72],
[NSNumber numberWithInt:73],
[NSNumber numberWithInt:74],
nil];
NSPredicate *aPredicate = [NSPredicate predicateWithFormat:#"type in %#", shipTypes];
[fetchRequest setPredicate:aPredicate];
theRecords = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
…when run, the executeFetchRequest message throws an exception…
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'unimplemented SQL generation for predicate : (type IN {71, 73, 70, 72, 74})'
Have I done something wrong, or is this really not supported?
I believe Alex is right that you have to use an NSArray, although obviously it'd be nicer if NSSet were accepted here, since order isn't that important (although it could conceivably affect how quickly the SQL can run).
As a side note, I never use the +predicateWithFormat: call in any code, ever, because it can't do compile-time sanity or type-checking. I highly advise using the individual classes.
In this case, I'd do:
fetchRequest.entity = [NSEntityDescription entityForName:#"myRecord" inManagedObjectContext:self.managedObjectContext]];
NSArray *shipTypes = [NSArray arrayWithObjects:[NSNumber numberWithInt:70],
[NSNumber numberWithInt:71],
[NSNumber numberWithInt:72],
[NSNumber numberWithInt:73],
[NSNumber numberWithInt:74],
nil];
fetchRequest.predicate = [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:#"type"] rightExpression:[NSExpression expressionForConstantValue:shipTypes] modifier:NSDirectPredicateModifier type:NSInPredicateOperatorType options:0];
theRecords = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
Not that this would have caught this particular error at compile time, but it WOULD have potentially caught it at the NSExpression level, thus making it much clearer what went wrong.
Hmm. I tried both adding the ANY keyword and using NSArray. Actually I was using NSAArray to start with when I got this exception from Core Data. In retrospect think that because I was combining two expressions with AND it was a problem for Core Data. (I know I should go back and verify this but this thought just occurred to me, while reading this post.) Here is my original expression:
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"BPM BETWEEN { %d, %d } AND GENRE IN %#", lower, upper, genres];
My solution was to break it up into two parts. Now, I issue the query for the BPM using the predicate but I take the resulting array and use -filteredArrayUsingPredicate with #"genre IN %#". Thus:
array = [array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"genre IN %#", genres]];
This works for me.
I struggled with this. Eventually I ended up doing a sub predicate for each of the set.
So for example:
NSMutableArray *subpredicates = [NSMutableArray array];
NSArray *shipTypes = [NSArray arrayWithObjects:[NSNumber numberWithInt:70],
[NSNumber numberWithInt:71],
[NSNumber numberWithInt:72],
[NSNumber numberWithInt:73],
[NSNumber numberWithInt:74],
nil];
for (NSNumber *num in shipTypes) {
[subpredicates addObject:[NSPredicate predicateWithFormat:#"ANY type == %#", num]];
}
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
That's assuming that type is a NSSet. If it's not you could just have:
for (NSNumber *num in shipTypes) {
[subpredicates addObject:[NSPredicate predicateWithFormat:#"type == %#", num]];
}
Try changing your predicate to #"ANY type IN %#"