Modelling short-term objects in Core Data app - ios

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"];

Related

Is it possible to include a [NSPredicate predicateWithBlock:] in a NSCompoundPredicate? [duplicate]

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];

Core Data, iCloud and Pre-Built Data with iOS 7

I'm a in a really common situation mostly driven by inexperience...
My application inserts pre-built data in the DB at the first launch.
I just check for db existence in the device... if it doesn't exist this is the first launch so I add my pre-built data after DB creation.
At the end of the development I decide to activate iCloud, wow! just one row of code to make it works... I tried it on different devices and my pre-built data were obviously duplicated :(
I understood that the better way to manage pre-built data is to work with more than one persistent store, creating read-only store for pre-built data (my pre-built data don't need to be modified by the user, so the read-only solution would be ok).
The problem is that I have relationship between data inserted by user so I can't work on multiple persistent store (core-data doesn't support relation between different stores).
I took a look at Fetched Properties but it seems to require a super drastic edit on my code.
I thought to go with de-duplicating data... so just removing data that are already in the DB, my doubt is that this process is really dangerous for relationship, how can I delete the object that is just a duplicate of the "original" created at the first launch?=
Given that my pre-build data are can't be modified or deleted by the users, which is in your opinion the best way to correct this problem?
// EDIT: Some information about the model:
The entity that users are dealing with (add/edit/modify)represents a TASK.
this entities have a name, and a CATEGORY.
CATEGORY are the entities that I pre-build.
So user add a name for the task and select a CATEGORY from the pre-built data.
In your Category entity, in the PrebuiltData model, create an "id" property (and make sure that this is consistent from version to version) This model should not be managed by iCloud.
In your Task entity, in the UserData model, create a "categoryId" property. This model should be managed by iCloud (or this discussion is meaningless)
Now you can create a method on your Category entity to fetch all the Tasks in the category using:
-(NSArray*)tasks
{
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:#"Task"];
request.predicate = [NSPredicate predicateWithFormat:#"id = %#", self.id];
return [gTaskManagedObjectContext executeFetchRequest:request error:NULL];
}
Likewise, you can create a method on your Task entity to fetch the category:
-(Category*)category
{
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:#"Category"];
request.predicate = [NSPredicate predicateWithFormat:#"id = %#", self.categoryId];
NSArray* results = [gTaskManagedObjectContext executeFetchRequest:request error:NULL];
return results.count > 0 ? results[0] : nil;
}

NSPredicate traversing relationship (StackMob)

I have been scouring Apple's Predicate Programming Guide and SO as well, trying to determine the correct way to write a predicate for a certain fetch request.
It seems like the way I am attempting to use dot notation to traverse a relationship is not being respected by the request.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Task" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"project.user == %#", self.user];
[fetchRequest setPredicate:predicate];
User, Project, and Task are all core data entities mapping to a BaaS provider (StackMob).
Relationships are as follows: User one-to-many Project one-to-many Task with inverse relationships as you would expect. What I am trying to do is get all tasks for the current logged in user. So, my thinking is: send a fetch request with the entity "Task". Where the project that owns the task is owned by the logged in user, get that task and display it. Simple enough.
And I think I should be able to use the dot notation in the predicate that I have shown above, but the fetch request is returning nil.
Everything else about the fetch request is fine. If I change the format string to #"project != NULL", I get all of the tasks back.
Am I just writing my format string incorrectly? Is dot notation somehow not valid when sending a fetch request to StackMob?
As always, any help is greatly appreciated.
Please let me know if more information is needed. I'm a little too close to this right now to see it clearly.
Fetching on an attribute of a relationship (task where project.user == X) is not supported by StackMob. However, it looks like you are trying to get all the tasks for a given project for the logged in user. If I am correct in assuming that the logged in user was the one to create the projects and tasks, then set the permissions of the project schema to "Allow to sm_owner". You can then fetch tasks based on the project ID (task where project IN 1234), or project based on ID and grab all tasks (project where projectId == 1234 and use value of tasks relationship). Either way, with that permission fetches will only return results where sm_owner == logged in user, so you don't need to set up the fetch such that it uses the user ID as a filter. Hope that helps!

Core Data fetch request and unsaved changes

i have a problem. My iOS app is behaving really strange when it comes to fetching some data and having unsaved changes. For your interest the whole behavior appears while syncing some data with a web server. I wanted to do a full sync and then save the changes. I tried some workarounds but none of them was working well enough.
To the problem itself:
I sync some entities with a web server. They are organized into zones (their parent), which themselves are in a building. So for each entity i query if a matching zone already exists, and if not i create a new one. The problem now is that i'm unable to fetch those zones if they were just created (so a new but identical zone is created everytime). I also have the problem that i cannot fetch the correct building anymore once it is changed by adding a newly created zone to it, the result for the exact same query is suddenly empty.
I have ensured that [fetch setIncludePendingChanges:YES] is set, and i'm also using normal result mode not NSDictionaryResultType (see: NSDictionaryResultType expression not taking into account newly inserted objects).
I hope somebody can help.
A Fetch request fetches data from a context that has saved data in the persistent store from which the context is fetching. When you create a new Managed Object, you create it in your context (a.k.a. your scratch book) but not in your persistent store, yet. So before you can fetch a newly created object, you must save the changes of that context into your store.
Assuming that I understand your description right: I think your predicate for fetching your data is pretty complex, which forces core data to read from the persistent store. Thus, modifications in the managed object context are ignored.
For example we have a data model like
Category 1---n Icon
and we want to fetch all categories which
have icons (more than zero),
have icons whose attribute usable is TRUE
have icons whose attribute enabledByAdmin is TRUE
we use a predicate like this:
NSArray *predicates = #[[NSPredicate predicateWithFormat:#"icons.#count > 0"],
[NSPredicate predicateWithFormat:#"ANY icons.usable = 1"],
[NSPredicate predicateWithFormat:#"ANY icons.enabledByAdmin = 1"]];
NSCompoundPredicate *cp;
cp = [[NSCompoundPredicate alloc] initWithType:NSAndPredicateType
subpredicates:predicates];
This complex predicate forces core data to read from the persistent store, directly.
My solution is to save the managed object context and fetch the data afterwards.

Core Data: object in a predicate

In my core data object model I have 3 entities with appropriate relationships so that MyObject can have many MyObjectProperties, and each property can have one MyObjectPropertyImage.
Given a myObject I want to fetch all the images.
I try to do it using the following predicate, however I get an empty array:
[NSEntityDescription entityForName:#"MyObjectPropertyImage" inManagedObjectContext:managedObjectContext];
[NSPredicate predicateWithFormat:#"ANY myObjectProperty.myObject == %#", myObject];
Any ideas?
When working with Core Data it's best to think of your entities as just that: entities in an object graph, instead of tables in a database. Therefore, you don't need to fetch entities related to others using a predicate. Instead, navigate the object graph using the relationships defined in the model. To get all the images related to myObject:
// assuming the relationships are called 'myObjectProperties' and 'myObjectPropertyImage', respectively
NSSet *allImages = [myObject.myObjectProperties valueForKey:#"myObjectPropertyImage"];
Note that this may trigger additional trips to the database if your object graph is not loaded in memory for your myObject entity. To avoid that, make sure you set the pre-fetching relationship keypaths in your fetch request for myObject.
I hope this helps...
Since you have a MyObject instance in hand and it has the relationship path of myObjectProperties-->ObjectProperty-->>PropertyImages you just need to traverse the relationships. It's easy to do this with valueForKeyPath:
Thusly:
NSArray *images=[myObjectInstances valueForKeyPath:#"myObjectProperties.propertyImage"];
(Note: I might have your attribute names wrong but you can get the idea.)
As general rule, you never fetch when have an object from the graph available. You fetch to "pick out thread" of objects matching the predicate and then to find all related objects you follow the thread/relationships to the related objects.

Resources