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];
Related
I am getting this Unsupported predicate error, while trying to instantiate a
NSFetchedResultsController with an aggregate Predicate.
My Coredata structure looks like this - There is a PurchaseDetails Entity which has n number of Payment Entity (One to many relationship). Each Payment has a type, for eg.Cash, Cheque , Other etc. Now I need to get the list of all the PurchaseDetails where Payment.type = Cash.
The NSPredicate looks like this: #"ALL payments.type = 'cash'"
Are we not supposed to use ALL ?
The predicate isn't crashing on creation rather it crash while executing the fetch request.
But the same fetch request works fine when used separately without using a NSFetchedResultsController. They both wont work together ?
Yes you need to use ALL if payments is a to-many relationship. Note that this means that every single payment's type will be #"cash". If you just want at least one cash payment, use ANY.
The (null) in the error message hints at something else though. Did you attach the predicate to the fetch request?
Also, did you try to set cache:nilin your fetched results controller? The FRC could be the reason you get stale results.
I'm facing this strange issue when trying to fetch some objects after their objectID. The error complains that there is no such thing as an objectID key path, but the managed object should respond to that. The error is not thrown all the time, which makes me think it could be a concurrency problem, although I've double checked and each context performs the operations on its own thread.
Here is the predicate, although it looks sane to me:
[NSPredicate predicateWithFormat:#"objectID == %#", book.objectID]
Edit: Regarding this answer. I didn't tried using the object itself, I need to use the objectID because of multithreading considerations.
The answer given in THIS link still holds here.
It holds in the sense that you use the same predicate:
NSPredicate* p = [NSPredicate predicateWithFormat:#"SELF = %#",book.objectID];
But you supply the objectID as parameter.
CoreData allow comparison of objectIDs and objects when fetching from the store. it probably has to do with the fact that an objectID is used for a one-to-one mapping to a managed object (hence the use of SELF which is a predicate reserved word and not a property name).
Background
I've got a core data database made of two stores (one repository for my data and one store for the user data), linked between them by fetched properties.
Let's say I have two entities such that the relationship between them is 0 to 1.
Card 0 ----> 1 CardStatus
1) Card, containing reference data and some properties (externalKey, word, description, ...)
Fetch Properties
- userData (pointing to CardStatus and using the externalKey to do the link between the 2 entities)
2) CardStatus, containing the status of each Card. This entity is stored in the user data store.
There are cases where I need to fetch cards based on their status (example: retrieve all the cards that are marked as expired, retrieved all the cards that are marked as new, etc...)
Question
What kind of predicate should I write to fetch Card entities based on values from the CardStatus entity?
I tried using my fetched property userData directly in the predicate, but it's not allowed by core data.
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: 'keypath userData not found in
entity '
Then I tried with a subquery (see below) - still doesn't work. Interestingly, it works fine when I do a filter directly on an NSArray (instead of a fetch request).
Example - Retrieving card with a specific status:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(SUBQUERY(userData, $x, ($x.currentLevel == 0)).#count > 0)"];
Example - Retrieving card marked as new (i.e. no data in CardStatus):
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(SELF, $x, $x.userData.#count == 0).#count > 0"];
Any suggestion?
I found the solution.
The query was correct and is now working fine. The problem was that I was using a child managedObjectContext in my thread, while those children contexts don't seem to accept the use of fetched properties.
I replaced this by (simply) creating a new managed object context directly based on my persistent store (in other words, this context and the context I'm using in the main thread are at the same level in the hierarchy).
Below is the code to illustrate what I'm describing:
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
NSManagedObjectContext *child = [[NSManagedObjectContext alloc] init];
[child setPersistentStoreCoordinator:[appDelegate.managedObjectContext persistentStoreCoordinator]];
It's now working all fine.
It seems to me that the CardStatus entity functions as some kind of enum to supplement the logic of your dynamic data store. I think this is not a good case for cross-store fetches. It seems a bit of overkill adding unnecessary complexity.
Rather, you could store the status in the Card entity directly as a normal attribute, maybe in a numeric format. You can use a cross-store fetch to get the name of the status from the static store if necessary.
I have a search bar and some filters in my app that create two predicates. One for the string in the search bar and one for the filter (UISegmentedControl).
When the predicates are used and I insert one object into the context and then call -save: the app crashes with:
CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. Can't use in/contains operator with collection 1379410732 (not a collection) with userInfo (null)
at the point of saving:
This does not happen when the predicates are not used.
Can I somehow remove the predicates during the process of saving? I already tried it by getting the NSFetchRequest, but I cannot set it back to the controller then.
Edit
I also tried to add self as an observer on the NSManagedObjectContextDidSaveNotification and then use -mergeChangesFromContextDidSaveNotification: like one discussion on SO suggested. This didn't help either.
I found my problem. It was quite easy, but the error message was so confusing that I didn't look at the right point.
The problem was that I was using [NSPredicate predicateWithFormat:#"number CONTAINS[cd] %#", string].
So the predicate was using CONTAINS on a NSNumber. The confusing part is that the predicates are working when querying existing data from the database. I still don't understand this part, but it's working now with == comparator.
I have an app that talks to the server to get some items (Item class) for current user and store it. So far so good.
I want to implement search, that essentially returns me a set of Item objects, but obviously I do not want to persist every search result there ever be. Another use case is that server API has different endpoints like recommendations/ new/ upcoming/ that return the same Item object, but in different context, so I would like to differentiate between them somehow.
My first thought was to use a throw-away managed context, load objects from API in there, do fetch and when user is done just destroy the context. Is it a good idea in general? It saves code, because most of my VCs already talk to core data.
Rather than throwing the whole wonderful infrastructure of Core Data away, you should leverage it to achieve your purpose.
Add a timestamp attribute to your entity and use it to selectively display search results or even purge your store from old items.
Add a category attribute to your entity and filter by category when searching.
Both can be achieved with an NSPredicate that you add to your NSFetchRequest. For example:
fetchRequest.predicate = [NSPredicate predicateWithFormat:
#"timestamp > %#", [[NSDate date] dateByAddingTimeInterval:numberOfSeconds]];
or
fetchRequest.predicate = [NSPredicate predicateWithFormat:
#"category = %#", #"new"];