26

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!

Bill Smithed
  • 261
  • 1
  • 3
  • 3
  • 9
    I noticed that the docs for `predicateWithBlock` state that "In Mac OS X v10.6, Core Data supports this method in the in-memory and atomic stores, but not in the SQLite-based store." No word on if/how this affects iOS (your scenario), though. You don't happen to run a SQLite store so that this *might* affect you? – ig2r Aug 22 '10 at 20:55
  • 6
    @ig2r That's actually the answer to the question. Yes, this is the case for iOS as well. Atomic stores will be opened and read, you will have all `NSManagedObject`s already created, so you can call messages to them. Same for in-memory stores. But with SQLite, `NSManagedObject`s are only created when there are requested from the store. An SQLite `SELECT` statement will be created based on a fetch request, taking the predicate into account. So the predicate is already applied before the objects have been created, thus you can't call messages on them. – Joost Aug 22 '10 at 21:30
  • @ig2r & Joost Can you please site where in documentation it is mentioned like that? I have a situation where I cannot avoid using predicateWithBlock to create the FetchedResultsController. – Raj Pawan Gumdal Aug 08 '12 at 05:39

2 Answers2

24

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.
ig2r
  • 2,396
  • 1
  • 16
  • 17
  • So how is this accomplished then, if not with a predicate? (sorry i'm new so it wouldn't let me respond to in the comments) – Bill Smithed Aug 23 '10 at 00:36
7

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];
Victor Jalencas
  • 1,216
  • 11
  • 23
  • 2
    but we can't use this 2-step-technique with an NSFetchedResultsController, right? – Martin Reichl Jan 10 '13 at 22:21
  • Exactlt Martin, the whole point is that you can use any of the benefits of the `NSFetchedResultsController`, like delegation of methods – Kevin R Apr 17 '14 at 12:25