I use Core Data on my iPhone app to store about 1000 objects. Each object is assigned one of 5 different groups. Over time, the objects change groups, and within each group, the order of the objects in those groups change and is stored in an array.
I then take this array and use the attribute 'name' to store it to a plist. When I relaunch the app, my AppDelegate scans each object in the database, then compares it to the 'name' attribute in my plist. When this completes, I have 5 arrays of core data objects, sorted by group, each in the original saved order. The end result is good. The time it takes to complete this task is not.
I want to make this faster. If I could save the actual core data object array to my plist, I would do that. But I can only save attributes to it.
Apple has documentation on Implementing Find-or-Create Efficiently, specifically, this code:
// get the names to parse in sorted order
NSArray *employeeIDs = [[listOfIDsAsString componentsSeparatedByString:#"\n"]
sortedArrayUsingSelector: #selector(compare:)];
// create the fetch request to get all Employees matching the IDs
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:
[NSEntityDescription entityForName:#"Employee" inManagedObjectContext:aMOC]];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat: #"(employeeID IN %#)", employeeIDs]];
// make sure the results are sorted as well
[fetchRequest setSortDescriptors: [NSArray arrayWithObject:
[[[NSSortDescriptor alloc] initWithKey: #"employeeID"
ascending:YES] autorelease]]];NSError *error = nil;
NSArray *employeesMatchingNames = [aMOC
executeFetchRequest:fetchRequest error:&error];
However, the fetchRequest sets a SortDescriptor and sorts using an 'employeeID' key. I tried this. I have an ID key attribute and can save the array index position when an object is added to a group. However, because objects are being added and removed from each group, the index of the object is constantly changing. So after each change, I would have to rescan each group and reset the index. To me, this would just move my speed problem to another part of my program.
My questions are: is there a better way to save the index position of objects in an array? or is there a different place I can store an array of core data objects? If I did the latter, though, because my app is already in the appStore, my understanding is if I add attributes or tables to the database, this can cause problems when a user upgrades.
I hope I explained this well. Any help would be appreciated, thanks.
You are making this way to complicated. The problem you want to solve i.e. ordering objects based on an attribute value, is a very common need e.g. every single tableview requires this type of ordering.
Ordering is why you can provide one or more sort descriptors to a fetch. In this case you want to order on an employeeID attribute so you just provide a sort that on that key. The array returned by the fetch will be sorted on that attribute.
When you add or remove objects, you just rerun the fetch again either directly or by using notifications. If you use a NSFetchedResultsController, this is all handled for you automatically.
If you need some kind of arbitrary ordering, say a user's list of favorites, then you need to model that arbitrary order directly in the data model. There are various means of doing so depending on the specific type of ordering needed.
Here is a good rule of thumb when designing with Core Data: If you have any other data structure to hold or order managed objects beyond the arrays returned by fetches, you've done something wrong.
Used properly, Core Data can managed the entire data model without any external structures. If you find yourself welding extra data structures onto Core Data, then you've missed something in your data model design.
On the iPhone there is no way to maintain an ordered collection other than storing an index. That property is an integer, correct?
If you add attributes what happens is you need to set up the persistantStoreCoordinator in a way that it attempts an auto-migration. If you never created another model version before though, this will be tricky. You should try that and see if you can get it to work as you are probably going to have to do it someday...
My feeling on CoreData is that the migration is tricky enough you should always keep any user generated data to the side in something like a plist also, that you can re-build a data base from. It sounds like you may already be doing that? If so, you can check when you create the persistantStoreCoordinator if it has failed, and if so then erase the database that is there, create the persistantStoreCoordinator again, and populate that database from your plists.
Related
I have a segment control with 5 items, on selecting every item data is filter on some criteria and a different result is displayed. All the five choices in segment control use the same entity to fetch the data.
Currently i have a fetchresultcontroller and whenever there is a value change in the segment control i fetch data from the same entity with a different predicate and reload the table with new data.
I am looking to optimize this. Am I doing it the right way or what is the right way to do it?
Also what is the best way to change the sorting order between ascending and descending for an already fetched data.
Thanks in adv.
Can you post some code snippets?
If you already fetched a set of MangedObjcts you can reorder the NSArray with a Sort Descirptor: sortedArrayUsingDescriptors
- (NSArray *)sortedArrayUsingDescriptors:(NSArray *)sortDescriptors
If you're only fetching a small number of NSManagedObjects, there probably isn't much optimization to be done. CoreData and its corresponding classes (such as the FetchResultsController you are using, which is designed to work particularly well with UITableViews) do most of the heavy lifting for you.
In terms of the best way to change the sorting order between ascending and descending; this is determined in something like this:
NSSortDescriptor *sorter = [[NSSortDescriptor alloc] initWithKey:#"someEntityProperty" ascending:YES/NO];
If you set ascending to YES, you'll get your NSManagedObjects back sorted smallest to largest ascending) on the key you provide. If you set it to NO, you'll get them back largest to smallest (descending).
I was looking at the TableSearch example code from Apple. It looks like that they have a NSArray for all the content, and a NSMutableArray for filtered content. And then if the filter is on, then they would show the NSMutableArray. If it is off, they would show the NSArray that has all the data.
1) I was wondering if this is a common implementation for filters since I haven't done much filtering before.
2) To add to that question, if I had a filter of four different categories, would I still use one NSMutableArray that shows the filtered content when the filter is on? Or do I create four different NSMutableArrays for each different type of filter, and then show that list depending on which filter is on.
Assuming that the common implementation is to have an NSArray for the list, I'm getting confused if creating the arrays of filtered list up front is expensive if I were to do four different NSMutableArrays, or if depending on the click from the user of what filter option they select, should I create the NSMutableArray on the fly, and then reload the [tableView reloadData];
Thanks.
I don't have that sample app in front of me, but you typically would filter using a predicate, so it would be helpful for you to review the docs on NSPredicate.
So when you want to change the filter, you do so by changing the predicate. You don't have to create all filtered results. You only create the one you need at any given moment.
With arrays, you can filter using code like that shown in this example. The key lines are
NSPredicate *predicate;
predicate = [NSPredicate predicateWithFormat:#"length == 9"];
NSArray *myArray2 = [myArray filteredArrayUsingPredicate:predicate];
Filtering is not always done with arrays. It can be done with NSFetchedResultsControllers if using Core Data. Predicates are used there also, in very much the same way. Predicates can be used for other things, too, including regular expression filtering. It's worth looking at, if you aren't familiar with it.
It really depends. If your underlying data is in Core Data, use NSFetchedResultsController and give it NSPredicates. If you have an array of data, it may be easiest to traverse it and create another array of data.
In general, the filter itself is not likely to be as expensive as the overall drawing process (which includes instantiating or recycling table cells). You can do what's easy and profile with Instruments.
Keeping four different arrays is normally not a good idea in terms of memory, which is a scarce resource.
No matter what though, reloadData is going to be involved. (Depending on OS version, perhaps — see the NSFetchedResultsController docs.)
This seems like it may be a trivial question, but I am new to Core Data and to databases.
In my application, I perform a fetch and display the results. Then, based on user input, I need to cull those results down. That is, I need to do a new search on only the results of the first fetch, but based on an entirely different parameter. (Sometimes, the second fetch will be based on an attribute, other times on a to-many relationship.) What is the optimal way to do this?
I have figured out two options to do this, but neither seems very good:
In the first fetch, prefetch all the data needed for the second fetch. Then, don't do a second fetch, but just iterate through the array of results of the first fetch, looking for matches to the new conditions of the second fetch. This method has the disadvantage that I have to trudge thru the array and don't take advantage of performance benefits of Core Data.
For the second fetch, disregard the first and do a brand new fetch with a compound predicate composed of the conditions for the first fetch and those for the second. This has the disadvantage that Core Data must look thru the entire database again to do the same search it already did.
Is there a way, in a second fetch, to tell Core Data to search only thru the entity objects returned in an earlier fetch?
You've pretty much got it figured out.
For the first option, it's pretty easy. You have your array of objects, and you can just do -[NSArray filteredArrayUsingPredicate:] or -[NSMutableArray filterUsingPredicate:] to reduce the array according to your needs. You don't need to actually iterate through the array yourself; just use a predicate like you would with a fetch request.
For the second option, that's also pretty easy. You take the predicate from your first request and AND it with your new predicate:
NSPredicate *original = ...;
NSPredicate *newCondition = ...;
NSPredicate *newFilter = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:original, newCondition, nil]];
Personally, I usually find the first option to be simpler overall.
This is on iOS.
I have a Core Database with about 350 000 objects. The objects (Product) have two properties: "Barcode" and "Designation". The user can search for an object by searching for the "Barcode", and the "Designation" should be returned. Everything is working fine, except it's slow. The code I use is:
NSEntityDescription *_product = [NSEntityDescription entityForName:#"Product" inManagedObjectContext:importContext];
NSFetchRequest *fetch = [[NSFetchRequest alloc]init];
[fetch setEntity:_product];
[fetch setPredicate:[NSPredicate predicateWithFormat:#"Barcode == %#",theBarcode]];
NSError *error = nil;
NSArray *results = [importContext executeFetchRequest:fetch error:&error];
NSManagedObject *object = [results objectAtIndex:0];
Since I only want to fetch one object, is there a way to speed it up?
If I load every object into an Array at the start-up I get a very slow start-up for the app and taking a lot of RAM.
Thanks in advance!
EDIT: I added [fetch setFetchLimit:1]; which speed it up a little bit. But the speed is getting slower the further down in the Database the object is.
Is the Barcode attribute indexed?
First, as #paulbailey wrote, check if Barcode is indexed.
But, if you have that many entries, and if your entry only has two properties (barcode and designation), and if you only query from the barcode side and return the designation side, using CoreData might be an overkill.
CoreData gives you a lot of object-oriented facilities with persistence to the disk, but it of course comes with a penalty.
It might be better for you to drop CoreData altogether, and use sqLite directely. There's a nice light-weight Objective-C wrapper called FMDB for that, see here.
If you want to stick to CoreData, one way to make things better is to fetch in the background thread and to show the result in the main thread, as described in this Apple document. This way the UI doesn't freeze while the database is searched.
The reason why it takes longer the further down the database the object is, is that Core Data uses a rather dull search algorithm which just places a pointer to the first object, comprehends its value to the searchitem, places the pointer to the next one and so one untill the comparison matches.
There are tons of search algorithms you can use, depending on your database (sorted/ not sorted lists, tree structure etc.) you could use Quicksearch, Hashsearches, treesearch and so on.
You might also think about setting up a SQlite database instead, which has some nice frameworks with intelligent search algorithms.
I'm writing a coredata based iPhone app that displays recipes. To improve performance, when displaying them in a TableView, I want to enable batching (-setFetchBatchSize:) and fetching only the "name" attribute (-setPropertiesToFetch:). When I turn on both, it doesn't work and the list is empty. As soon as I comment out one of the lines marked in the code below, it works fine.
What am I missing here? Is it impossible to have both?
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Rezept" inManagedObjectContext:chk_context]];
// *snip*
//BATCHING
[fetchRequest setFetchBatchSize:25];
NSDictionary *entityProperties = [[NSEntityDescription entityForName:#"Rezept" inManagedObjectContext:chk_context] propertiesByName];
//PROPERTIES
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:[entityProperties objectForKey:#"name"]]];
I am far from a core data expert, but I got this to work successfully in my situation. I think the "conflict" between setFetchBatchSize and setPropertiesToFetch is more a side-effect of how core data works and not a bug per-se.
In my case, I did two fetches. In the first one, the result type was set to NSManagedObjectResultType and I used setFetchBatchSize to limit the amount of data actively brought into memory. In the second fetch, I populate an array of titles based on a single attribute and set the result type to NSDictionaryResultType and fetchBatchSize to 0 (infinite).
Based on testing, this scenario works perfectly. All the records in the initial fetch (with the actual managedObjects) are faulted and memory-limited by the fetchBatchSize. The second fetch returns a simple dictionary of titles. This takes much less memory than iterating through all the actual managedObjects to access the title attribute. It makes sense that the second fetch needs fetchBatchSize disabled as it returns the fully populated dictionary as a single result and batching wouldn't be appropriate.
I'm not sure I'm being 100% clear here (core data terminology is a bit arcane...) but the bottom line is that I think everything is working as intended.
Looks a lot like you found a bug in CoreData. You can verify for sure by turning on SQL logging - I'm guessing turning on both options generates slightly invalid SQL.
The option you want to use is "com.apple.CoreData.SQLDebug 1" - you can specify this from the command-line, or set the default on your program.
-Wil
Update: When I NSLog the error after
[fetchedResultsController performFetch:&error];
I get "NSUnderlyingException = The database appears corrupt. (invalid primary key);". But I don't know what that means and what it has to do with enableing both methods.
Hunter you can use com.apple.CoreData.SQLDebug 1 on the phone but not using the simulator