NSSet from attributes of objects (performance) - ios

I am trying to create a NSSet of strings collected from an unknown (possibly large) number of objects' attributes.
The user of my app logs objects during the month - they have different attributes wheras i am only interested in the NSString *category name.
Object.h
NSString *category
...
During a month he may log between 10 and 1000 such objects, stored in coredata. The user can define own categories - and i have to find out which categories were used during that month (to create an export file).
Currently i do the following (pseudocode)
NSArray *allObjects = [_dataHandler fetchAllObjectsForMonth:monthToExport];
NSMutableSet *allCategoryNamesSet = [[NSMutableSet alloc]init];
for(Object *obj in allObjects){
[allCategoryNamesSet addObject:obj.category];
}
Wheras this works, it gets really slow with a lot of objects as the fetching takes time and the iterating as well of course.
I have tried something like that as well:
NSArray *categories = [allObjects valueForKeyPath:#"category"];
NSSet *allCategorieNamesSet = [NSSet initWithArray:categories];
maybe i did something wrong but it didnt quite work :/
What i am interested in is, if there is a solution that could significantly speed up this process? something i might have overlooked.
Any ideas?

If you are interested only in the (distinct) value of the category attribute, set
[request setResultType:NSDictionaryResultType];
[request setPropertiesToFetch:#[#"category"]];
[request setReturnsDistinctResults:YES];
for your fetch request. The fetch request then return an array of dictionaries (instead of managed objects) containing the category values.
The advantage is that this "filtering" is done on the SQLite level. One disadvantage (or "caveat") is that this implies setIncludesPendingChanges:NO, i.e. the fetch request is done only against the saved database and does not include unsaved pending modifications.

Checkout MagicalRecord, which is a convenience library for working with Core Data. This should help you do a lot of these tasks.
Good luck.

Related

Single fetchRequest for multiple entities - use relationships and/or entity inheritance?

I have a simple app which uses Core Data. As the user progresses through each ViewController, the managedObject gets passed through each view then saved to the store on completion. At the minute my Model has a single Entity with over 100 properties, which I would now like to separate into multiple entities. I am fairly new to iOS programming and not overly confident with database work, so apologies if my terminology is not correct.
An example of how my app works: On my ClientViewController, I declare managedObjectNGLS which currently stores all attributes.
// Identify the app delegate
NGLSAppDelegate *appDelegate = (NGLSAppDelegate *)[[UIApplication sharedApplication]delegate];
// Use appDelegate object to identify managed object context
NSManagedObjectContext *context = [appDelegate managedObjectContext];
// Create new managed object using the NGLS entity description
NSManagedObject *ManagedObjectNGLS;
ManagedObjectNGLS = [NSEntityDescription insertNewObjectForEntityForName:#"NGLS"
inManagedObjectContext:context];
// Declare managed object
self.managedObjectNGLS = ManagedObjectNGLS;
This gets passed through to ServicesViewController.
// Allocate & initialise ServicesViewController
ServicesViewController *services = [[ServicesViewController alloc]initWithNibName:#"ServicesViewController"
bundle:nil];
// Pass managedObject to view
services.managedObjectNGLS = self.managedObjectNGLS;
// Push next view
[self.navigationController pushViewController:services animated:YES];
Once the user has selected the desired services, the managedObjectNGLS gets stored to my Model.
[[self.managedObjectNGLS managedObjectContext] save:&error];
In the AdminViewController, the user can export the entire contents of the NGLS entity using a fetchRequest. Please see this post on how I am achieving this (apologies if this seems like a duplicate post, but I am still trying to understand all of this).
My question: I would very much like someone to explain the best solution regarding my entity situation. I have tried to set a Parent Entity which seems to work fine when only two entities are involved, but when more are introduced I get lost. I am not sure about relationships or an abstract entity?
Ideally I would like to have separate entities for NGLS (holds various important attributes like date stamp), Admin (username, site location, etc), Client, Employer (possibly separated further into Employer1,...,Employer10 eventually), and Services. I am not sure whether to create an Abstract or Parent entity with no attributes, then just hold relationships to that with the separate entities?
I have laid out what I would expect the entities to look like in the editor, but I am unsure if this is correct. The idea of the app is to store details about one Client per time, so on that basis the Client can have many Employers and many Services. I have implemented an extremely simple "login" system where the lastObject in my Admin entity gets stored in the NGLS entity (see this post for a description), so the Admin entity doesn't necessarily have a relationship with anything (I may be wrong here?).
I have tried the following code but it just crashes the app:
NSMutableArray *entityArray = [NSMutableArray array];
for (NSString *entitySuffix in #[#"NGLS", #"Client", #"Services", #"Employer"]) {
NSString *entitySelect = [NSString stringWithFormat:#"%#", entitySuffix];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entitySelect];
[entityArray addObjectsFromArray:
[context executeFetchRequest:request error:nil]];
}
// CHCSVParser
NSOutputStream *stream = [[NSOutputStream alloc]initToMemory];
CHCSVWriter *writer = [[CHCSVWriter alloc]initWithOutputStream:stream
encoding:NSUTF8StringEncoding
delimiter:','];
// Fetch objects to write to .csv
for (Model *results in entityArray) {
[writer writeLineOfFields:#[results.site,
results.username,
results.dateStamp,
results.clientTitle,
results.clientForename,
// results from different entities
Again, I apologise for this post as it seems like I am asking for an outright answer, but I really just need someone to point me in the right direction on how to set up separate entities and use a single fetchRequest to fetch data from the entire Model with all of it's entities. Thanks in advance!

RestKit, Core Data, and Relationship problems. Parent object not recognizing child set

I am trying to map a relationship between Articles and the Photos that belong to them using RestKit. Both objects get stored properly when requesting the resource, but it seems the relationship does not persist. In fact, the Article model seems to not even respond to the Photos selector (This may be the 'duh' spot, but I will provide full code to be through).
I've provided all code in a gist, as I find it easier to look through and format then on StackOverflow. Sorry if this is actually an inconvenience.
https://gist.github.com/3733334
And here is the image of the core data model and the relationships set up (sorry, I had to combine them since I can only post 2 hyperlinks currently):
http://imageshack.us/a/img33/5039/stackoverflowissue.jpg
Everything seems to be working properlly except the relationship between the objects when I try to access photos via anArticle.photos. The selector is unrecognized. I set up a convience method in the Photo model to return all photos with a matching article ID, but I feel this is an un-ideal solution as it sort of removes the whole idea of creating a relationship.
I feel there may be something simple I am missing and any help would be greatly appreciated.
Thanks!
So of course it was a "Duh" error. After some help from a local developer, he pointed out that my Photos variable in my Article.h file was an NSArray, and needed to be changed to an NSSet to store objects mapped by RestKit.
Theres some inconsistency between different versions of RestKit. If you are using the latest one mappings should be set up as shown here: https://github.com/RestKit/RestKit/wiki/Object-mapping. If you want to use entity classes for model specific methods make categories on your NSManagedObjects so that when you change your data model you can regenerate them (Do this only after you extract your methods to a category! Select an entity in your .xcdatamodeld and go to Editor -> Create NSManagedObject Subclass...).
I moved my mappings to the controller that is responsible for syncing with the remote API.
This shuld be helpful too: http://andriyadi.me/logging-in-restkit/.
Also Core Data guidelines stress that you should set up inverse relations (but it's not obligatory).
Fetching entities can also be done better in my opinion. In my project I have an NSObject subclass singleton that (among some other Core Data convenience functionalities) fetches by entity and predicate:
- (NSArray *)fetchEntities:(NSString *)entity usingPredicate:(NSPredicate *)predicate {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entity];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
if (error) {
RLog(#"Error fetching entity %# using predicate %#", entity, predicate);
abort();
}
if ([results count] >= 1) {
return results;
}
return nil;
}
You can alter it to pass predicates as NSStrings too.
Side note:
Currently I'm also struggling with RestKit and object mapping myself :).

Core Data: With a NSFetchRequest Is there a way to get fetched properties when the result type is set to NSDictionaryResultType

I have an application that uses two separate core data stores. One for user data, and one for read-only content data. In order to create relationships between them, it is of course forbidden to use relationships, and so references to the content store are managed by ids.
This works fine in most cases, but for some particular fetches it is proving difficult.
Take the following example: Say I have an application that has a few hundred movies (with a record for each stored in the read-only store) and whenever a user watches one a record is created in the writable user store. I might set up my model to have one Entity called Movie, and another called MovieHistory.
Movie has:
an attribute called identifier (NSNumber).
MovieHistory has:
an attribute called movieIdentifier (a cross-store reference to Movie),
a viewDate attribute (NSDate),
and a fetched property 'movie' with the destination set to Movie and a predicate of (SELF.identifier == $FETCH_SOURCE.movieIdentifier).
Say I now want to get the last 10 movies watched, without duplicates (if I watch a movie that I previously watched it should jump to the top of the list). I'd like to be able to use the following code:
NSError *error = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: #"MovieHistory"];
NSSortDescriptor *dateDescriptor = [[NSSortDescriptor alloc] initWithKey:#"viewDate" ascending:NO];
NSEntityDescription* entity = [NSEntityDescription entityForName:#"MovieHistory" inManagedObjectContext: moc];
NSAttributeDescription* movieIdentifierDesc = [entity.attributesByName objectForKey:#"movieIdentifier"];
NSFetchedPropertyDescription *movieDesc = [entity.propertiesByName objectForKey:#"movie"];
[request setFetchLimit: limit];
[request setSortDescriptors: [NSArray arrayWithObject: dateDescriptor]];
[request setPropertiesToFetch:[NSArray arrayWithObjects: movieIdentifierDesc, movieDesc, nil]];
[request setPropertiesToGroupBy:[NSArray arrayWithObject: movieIdentifierDesc]];
[request setResultType:NSDictionaryResultType];
NSArray *results = [moc executeFetchRequest: request error: &error];
if(error != nil)
NSLog(#"Error fetching last viewed movies: %#", error);
return results;
This code throws an exception stating that the fetched property ('movie' in 'MovieHistory') is an invalid property to use in this case. You can't use fetched properties when using NSDictionaryResultType. However, you also can't use setPropertiesToGroupBy: without the return type being NSDictionaryResultType. I need grouping in order to sort by date and remove duplicate Movies.
It works fine when I remove the fetched property from the array sent to setPropertiesToFetch:. But then of course I would have to pull out each MovieRecord one-by-one in a loop.
What is the best way to accomplish what I'm trying to do? Is there a way to do this without having to resort to fetching the IDs and then looping through the results one-by-one?
Thanks,
I didn't exactly follow your whole post, however...
You can issue two separate fetch operations. The first gets the MovieHistory objects from that database.
Put all the movie identifiers into a collection, and then fetch on the movie database, using the IN keyword.
NSPredicate *moviePredicate = [NSPredicate predicateWithFormat: #"identifier IN %#", allMovieIdentifiers];
It is not quite as elegant as some other solutions, but it is easy to write, and, more importantly, easy to read, understand, and change. Remember optimize first for programmers.

Momento and 1 to Many Joins : CoreData Approach, Design Considerations and Opinions Sought

Given and coredata based app using an Indexcard metaphor. Each Indexcard can optionally have a one-to-many relationship with a number of other entities/tables; i.e. I'll use Momento's 'Moment' as a proxy for my Indexcard object and Momento's ancillaries of tags, locations, etc. , for these other objects/tables.
What is the 'fastest' way to show whether or not these foreign table relationships exist on probably the most important tableView in the entire app?
and
What would be the best approach for laying out the cell portion showing whether or not a relationship exists and the count of the number of each type of relationship?
Again, using Momento as a design pattern. With a link to a screenshot on Flickr (because stackOverflow won't let me post an image since I'm a noob.)
Maybe my ex-RDBMS stuff is contaminating my thinking, but they didn't do a mongo-join to get the values off to the right did they? [tags,events,people,locations]. There has to be a more elegant way that I'm just not seeing.
My thoughts for laying out the cells on the right was to possibly use some boolean if YES put up the icon and the count, but that seems pretty expensive for every cell.
I'm sure that the answer to this layout question would be driven by the approach taken in the first part of the problem. It doesn't seem that I would want to store ancillary relationships in the 'main/moment' IndexCard object for maintenance reasons.
Thanks in advance for any help.
If you set up a one-many relationship of indexcards to tags(or whatever), a fetched indexcard object should have an NSSet of tags as a property. Same for the others, and you should just be able to get the count of the set and display that next to each of the appropriate icons.
..Unless I'm misunderstanding your question.
edit: to answer the second part, you should indeed have a conditional in cellForRowAtIndex path that checks the count of each set and either just display it with the icon (possible to have 0 then, which is normally fine), or check whether it is 0 and hide the image if it is as you said. I don't think either solution will slow down your app since the data has already been fetched anyway by the time the cell is being rendered, but the solution where you just pass the count right through without checking if it's 0 would generally be fast overall.
edit to provide some sample code:
Your Core Data model would have an IndexCard entity and then an entity for each type of possible related object.
1)Model:
IndexCard - has a one-many relationship with each of the other entities
Tag
Location
Person
2)After creating this model and the corresponding Object classes you will end up with an IndexCard class that has the following in its header
#property (nonatomic, strong) NSSet *tags
#property (nonatomic, strong) NSSet *locations
#property (nonatomic, strong) NSSet *people
and of course the following in its implementation
#dynamic tags
#dynamic locations
#dynamic people
3)Now that we've established this Core Data model, we can perform an nsfetchrequest (of course when using a tableview, you should use an nsfetchedresultscontroller as it will dynamically fetch the IndexCards it needs as you're scrolling through the table). This code assumes that we have a usable NSManagedObjectContext in its scope (ideally passed in from the AppDelegate and set as an ivar) and that our IndexCard object has some sort of key/id property we can search by, lets call it "number"
NSNumber *numberWeWant = [NSNumber numberWithInt:1];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"IndexCard" inManagedObjectContext:ourContext];
request.predicate = [NSPredicate predicateWithFormat:#"number == %#", numberWeWant];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
//didn't bother error checking in case no IndexCard matches
IndexCard *ourCard = [results lastObject];
//you can now use these to display in the cell or hide the appropriate icons if they = 0
int numTags = ourCard.tags.count;
int numLocations = ourCard.locations.count;
int numPeople = ourCard.people.count;
//just a sample of how we would access the individual related objects
for(Tag *tag in ourCard.tags)
{
//do whatever you want with each tag here
}
Again, this code is just to fetch a single IndexCard. In an actual table you would be initializing an nsfetchedresultscontroller when loading the view that contains it, and then just accessing the IndexCard at the position matching IndexPath.row in cellForRowAtIndexPath.
This also assumes there are a finite number of types of objects that IndexCard can be related to. If the types can change and increase randomly, this approach would need to be modified.
Hopefully this helps.

How to prevent Core Data making duplicates in iOS 5?

I've run into a problem.
Over the weekend I've been working on a project where I'm pulling a large xml from a webservice.
It basically has 3 tiers - Clients, Managers, Staff all hierarchical. So the first time the app runs, it pulls this xml and parses it and creates all the entries in the 3 releated Entities - Clients, Managers and Staff.
Every time the app launches I need to pull that same XML down, but this time, I only need to 'update' any of the existing records that have changed, or add new ones for new clients, managers or staff that have appeared since last time.
So - at the moment, as I said, it's pulling it all, parsing it correctly and creating the correct entities and filling in all the attributes.
However, with no data change, on the 2nd launch it's DUPLICATING all of the data - so instead of 15 clients ( the correct number ) I have 30 and so on...
Do I really have to add lots of code in my parsing to check that instead of creating a new NSManagedObject, I check if it's already there?
And if it is - I have to then manually check every attribute?
That's awfully painful and longwinded - isn't there a way to make Core Data do this kinda stuff for me - automatically?
Thanks for any help or suggestions.
I fear you have to keep your DB clean by yourself … The easiest way would be using NSFetchRequest: When importing your updated data you can run a query against the existing data and decide what you want to do.
As Marcus S. Zarra mentioned in another thread about this topic:
When you are importing a new row you can run a query against the
existing rows to see if it is already in place. To do this you create
a NSFetchRequest against your entity, set the predicate to look for
the guid property and set the max rows returned to 1.
I would recommend keeping this NSFetchRequest around during your
import so that you can reuse it while going through the import. If the
NSFetchRequest returns a row you can update that row. If it does not
return a row then you can insert a new row.
When done correctly you will find the performance more than
acceptable.
Another source for good information are Apples Programming Guides: Core Data Programming Guide
As Stated in Apple Docs https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html
You need to loop the data model and handle it from there like this
Example:
// loop over employeeIDs
// anID = ... each employeeID in turn
// within body of loop
NSString *predicateString = [NSString stringWithFormat: #"employeeID == %#", anID];
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString];
Personally I do not like this method and I wrote this snippet of code that handles this in a pro-efficient manor and which is straight forward! I noticed with Apples method I ran into issues with strings having different characters such as capitol letters and spaces. Below code is tested and working if you rename all your corresponding objects correctly I honestly believe this is the most efficient way to accomplish not adding duplicates in core data.
-(void)AvoidDuplicatesinDataModel
{
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Users"
inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"users"
ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
// Fetch the records and handle an error
NSError *Fetcherror;
NSMutableArray *mutableFetchResults = [[managedObjectContext
executeFetchRequest:request error:&Fetcherror] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
// This is a serious error
}
//here usersNameTextField.text can be any (id) string that you are searching for
if ([[mutableFetchResults valueForKey:#"users"]
containsObject:usernameTextField.text]) {
//Alert user or handle your duplicate methods from here
return;
}
}

Resources