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
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.
I want a thorough list regarding comparison between the two. Things I have known:
executeFetchRequest:
Message sent to MOC
Return an array of managed objects
Goal: fetch objects from persistent store to MOC
With table view: has nothing to do with table view
Frequency: often used in a loop, so could be called many many times
performFetch:
Message sent to FRC
After calling it, use fetchedObjects to return an array of managed objects
With table view: FRC is specifically for keeping managed objects and table view rows in sync, and use performFetch to initialize that process.
Frequency: often only once. Unless fetch request of FRC changes, no need to call performFetch a second time
Please correct me if I am wrong and append the list. Thank you.
About executeFetchRequest:
Message sent to MOC
Yes
Return an array of managed objects
Yes, but you can also change the type of results you want to retrieve. In NSFetchRequest you can set a different result type with:
- (void)setResultType:(NSFetchRequestResultType)type
where NSFetchRequestResultType can be of different types. Taken from Apple doc:
enum {
NSManagedObjectResultType = 0x00,
NSManagedObjectIDResultType = 0x01,
NSDictionaryResultType = 0x02
NSCountResultType = 0x04
};
typedef NSUInteger NSFetchRequestResultType;
Goal: fetch objects from persistent store to MOC
Yes, creating a NSFetchRequest and performing a request, it the same as creating a SELECT statement in SQL. If you also use a NSPredicate it's the same as using SELECT-WHERE statement.
With table view: has nothing to do with table view
Yes, but with retrieved data you can populate a table
Frequency: often used in a loop, so could be called many many times
It depends, on what you want to achieve. It could be within a loop or not. Executing the request within a loop could have impact on performance but I would not be worried on that. Under the hood Core Data maintains a sort of cache mechanism. Every time you perform a request, if data are not in the cache, Core Data executes a round trip on your store (e.g. sql file) and populate the cache with the objects it has retrieved. If you perform the same query, the round trip will not performed again due to the cache mechanism. Anyway, you could avoid to execute a request within the run loop, simply moving that request outside the loop.
About performFetch:
Message sent to FRC
Yes
After calling it, use fetchedObjects to return an array of managed
objects
Yes, but you can also retrieve an object with [_fetchedResultsController objectAtIndexPath:indexPath]; if you are populating a specific cell within a table.
Here I really suggest to read a nice tutorial on NSFetchedResultsController
With table view: FRC is specifically for keeping managed objects and
table view rows in sync, and use performFetch to initialize that
process.
Yes, a NSFetchedResultsController works in combination with a NSManagedObjectContext for you. Furthermore, it enables lazy loading of data. Suppose you have 1000 elements you retrieve and you want to display them in a UITableView. Setting a request for a NSFetchRequest like:
[fetchRequest setFetchBatchSize:20];
and using it with an instance of a NSFetchedResultsController, it allows to load 20 elements at first. Then when you scroll, other 20 elements are loaded, and so on. Without a NSFetchedResultsController you must implement this behavior manually. Refer to the tutorial I provided for further info.
Frequency: often only once. Unless fetch request of FRC changes, no
need to call performFetch a second time
It depends on what you want to achieve. Most of the time you could call it once.
Hope that helps.
Edit
You have to call performFetch explicitly. I like to create a property for NSFetchedResultsController in my header file (.h) like
#property (nonatomic, strong, readonly) NSFetchedResultsController* fetchedResultsController;
and synthesize it in your implementation file (.m) like
#synthesize fetchedResultsController = _fetchedResultsController;
Then always within the .m file override the getter to create an new instance of it:
- (NSFetchedResultsController*)fetchedResultsController
{
// it already exists, so return it
if(_fetchedResultsController) return _fetchedResultsController;
// else create it and return
_fetchedResultsController = // alloc-init here with complete setup
return _fetchedResultsController;
}
Once done, within your class (for example in viewDidLoad method) use it like
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
// Handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
You are comparing the wrong elements. NSFetchedResultsController uses the NSManagedObjectContext to perform the fetch, and under proper configuration, monitors the changes in the managed object context to verify the status of the fetch properties it is monitoring, but the actual fetches are done by the context. On both cases, NSManagedObjectContext does the fetch. The difference being that, using the NSManagedObjectContext directly, you get an NSArray type of object (the actual runtime class is different than an array you get using [NSArray array]), while NSFetchedResultsController has a different purpose (have a collection of results and monitor changes to the records and entity on its fetch request). In other words, NSFetchedResultsController works using the context, but it works different than just a simple collection of objects.
One observation: you shouldn't be using executeFetchRequest inside a loop, especially calling it "many many times". Each fetch has its performance cost. You can call executeFetchRequest once, and do a loop to check the result.
I have run into a weird problem with CoreData on MacOsX 10.6 using an SQL store. I have an NSManagedObject subclass called Family with attribute name and a relationship personList connected to another NSManagedObject subclass called Person with attribute firstname and inverse relationship family. A Person has only one family, and a family can have several Persons.
Say I have a Family object family pointing to the family 'Doe' with 2 Person (John and Jane) connected to it and I do the following request:
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:#"Person" inManagedObjectContext:managedObjectContext]];
[request setPredicate:[NSPredicate predicateWithFormat:#"family.name=%#",[family name]]];
NSArray *array = [managedObjectContext executeFetchRequest:request error:&error];
I get an array of size 2 with the 2 persons: Jane and John, with Family name Doe. Now, if I update the Family using its synthesized accessor, in my case:
[family setName:#"Wheat"]
I cannot after get the list of Person using the same fetch request. The results is an [array count] of 0.
If I change the predicate to the following line, it works again:
[request setPredicate:[NSPredicate predicateWithFormat:#"family=%#",family]];
So it is as if the Predicate is not using the updated version of the property name of the family, even though I have the NSFetchRequest set to the defaults (so includesPendingChanges returns YES). That makes no sense to me. It seems that the NSFetchRequest finds the family object, but fails to see that its value family.name has been updated, not saved, and is in the managedObjectContext in memory. Of course, if I save the store, then it works again.
Any idea? I have been through the Mac documentation and cannot see why this would fail.
I think the key here is understanding the fetch request. It retrieves data from the persistent store, so clearly, if you did not save to the persistent store, it will not find that data. The situation you describe is entirely logical if you take that into account.
From the Core Data Programming Guide:
You cannot fetch using a predicate based on transient properties
(although you can use transient properties to filter in memory
yourself). Moreover, there are some interactions between fetching and
the type of store—for details, see “Store Types and Behaviors.” To
summarize, though, if you execute a fetch directly, you should
typically not add Objective-C-based predicates or sort descriptors to
the fetch request. Instead you should apply these to the results of
the fetch. If you use an array controller, you may need to subclass
NSArrayController so you can have it not pass the sort descriptors to
the persistent store and instead do the sorting after your data has
been fetched.
To summarize, I have been testing thoroughly my code and here is how I perceive the limitations of CoreData regarding Fetching and objective-c predicated (ie the dot notation).
If an object has been access by the Objective-C program and if one of its property or relationship has been modified, any NSFetchRequest with a predicate using a dot notation will return the structure of the SQL store, hence the results will be erroneous.
In the case of the trivial Family and Person example, if you have a link to a Family and change its name, any query made on the Person NSEntity cannot include a predicate with the following query item
#"family.name=%#"
It will indeed query using the family name in the SQL store. However, the following query will work after such a change:
#"family=%#"
Indeed, the NSFetchRequest will still retrieve the info in the store, but since the structure has not changed, it will replace the objects retrieved by those in Memory, so a subsequent test to [family name] will return the updated name.
With care, you can use nested Predicate such as:
#"person.family.name=%#"
As long as you can guarantee that all the objects that have the property person, have not their family altered, nor their name altered. If it's not the case, then you can at best call
#"person.family=%#"
Or if you can't guarantee that all the Family objects are untouched, only
#"person=%#"
Of course, an alternative is to systematically SAVE: the NSManagedObjects to the persistent store every time you make any change, so all properties are updated and then all the above notations would work. There are times however when you do want to prevent savings and force the customer to change it's document only if he wishes (think about Word, Excel, Picture tools, etc..). Hope this is of help.
If by "using the same fetch request", you mean using the very same instance of the fetch request that you constructed the first time, then this is no surprise. The predicate you applied is "family.name = Doe". Once the family's name is "Wheat", the fetch request's predicate no longer matches it, because "Wheat" != "Doe".
To retrieve the family after changing its name, you would need to create a new instance of NSFetchRequest using a predicate matching the new family name.
If by "using the same fetch request", you mean using a different fetch request constructed using the same code, well, then I would think about #Mundi's answer.
I have a ManagedObject class, and one of the members of the class is a NSDate. I would like to display all objects of the class for which the date is NOT set. I tried using a predicate like this:
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"(date = NIL)"];
But I still get objects where the date is set. What is the right way to set up a predicate for this?
I think it's a case sensitivity issue. You can use "nil" or "NULL", but not "NIL". This works fine for me:
NSPredicate *eventWithNoEndDate = [NSPredicate predicateWithFormat:#"endDate = nil"];
Figured it out. Couldn't do it by using a predicate with a string format, so tried a predicate with a template and it worked. Here's the code that gave me objects that had endDate set to NULL:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"endDate = $DATE"];
predicate = [predicate predicateWithSubstitutionVariables:
[NSDictionary dictionaryWithObject:[NSNull null] forKey: #"DATE"]];
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Predicates/Articles/pUsing.html#//apple_ref/doc/uid/TP40001794-SW4
following code should work
predicate = [NSPredicate predicateWithFormat:#"firstName = nil"];
There's a super annoying behavior of fetch requests, as documented by Apple:
If an object in a context has been modified, a predicate is evaluated against its modified state, not against the current state in the persistent store. Therefore, if an object in a context has been modified such that it meets the fetch request’s criteria, the request retrieves it even if changes have not been saved to the store and the values in the store are such that it does not meet the criteria. Conversely, if an object in a context has been modified such that it does not match the fetch request, the fetch request will not retrieve it even if the version in the store does match.
It's possible you're clearing the date elsewhere and the fetch request is including results where the date is nil in memory but still set on disk (in the persistent store), and so when the object faults it loads the object with the date set.
My only advice would be to coordinate access to the managed object context (say, on an NSOperationQueue) such that any updates are able to be saved to the persistent store before executing the fetch request.
I can create an NSPredicate easily using an NSPredicateEditor (a subclass of NSRuleEditor). What I'd like to know is this:
How can I take an existing NSPredicate (one created by the editor) and reload it into the editor so that I can alter it?
EDIT: I tried #John's suggestion of using setObjectValue:, but that didn't quite work. Let me explain my set up a bit more:
I've got a Document-based cocoa app, and the Document window just has an NSPredicateEditor on it. In the dataOfType:error: method, I have:
NSPredicate * pred = [predicateEditor objectValue];
NSData * predicateData = [NSKeyedArchiver archivedDataWithRootObject:pred];
return predicateData;
In the readFromData:ofType:error: method, I have:
NSPredicate * pred = [NSKeyedUnarchiver unarchiveObjectWithData:data];
[predicateEditor setObjectValue:pred];
return (pred != nil);
I've verified that the predicate is getting correctly archived and unarchived, but after opening a saved predicate, the predicate is not loaded into the predicateEditor. (Yes, predicateEditor is hooked up as an IBOutlet)
Set the objectValue property of the NSPredicateEditor to the predicate in question.
The documentation has this description of the loading process; does any of this seem like it might cause a problem with your setup?
First, an instance of
NSPredicateEditor is created, and some
row templates are set on it—either
through a nib file or
programmatically. The first thing
predicate editor does is ask each of
the templates for their views, using
templateViews.
After setting up the predicate editor,
you typically send it a
setObjectValue: message to restore a
saved predicate. NSPredicateEditor
needs to determine which of its
templates should display each
predicate in the predicate tree. It
does this by sending each of its row
templates a matchForPredicate: message
and choosing the one that returns the
highest value.
After finding the best match for a
predicate, NSPredicateEditor copies
that template to get fresh views,
inserts them into the proper row, and
then sets the predicate on the
template using setPredicate:. Within
that method, the
NSPredicateEditorRowTemplate object
must set its views' values to
represent that predicate.
NSPredicateEditorRowTemplate next asks
the template for the “displayable
sub-predicates” of the predicate by
sending a
displayableSubpredicatesOfPredicate:
message. If a template represents a
predicate in its entirety, or if the
predicate has no subpredicates, it can
return nil for this. Otherwise, it
should return a list of predicates to
be made into sub-rows of that
template's row. The whole process
repeats for each sub-predicate.