views:

165

answers:

2

Hi, my app shows a map with annotations fetched from core data. Currently I do performFetch in viewDidLoad for the map, however, this is delaying the map from appearing immediately on the screen when I select its tab in my tabViewController. Is there a more appropriate place to call performFetch or is there something else I'm doing wrong? It's returning approx 250 objects from Core Data.

Thanks

+1  A: 

There are other places like viewDidAppear: but they would also lock up the user interface if you run long queries.

One thing you can do is execute the fetch on a background thread and then ask the view controller to display the data on the main thread.

St3fan
Thanks, I performed the fetch on a background thread and that works nicely.
Griffo
+1  A: 

In addition-to/instead-of running threading, you could restrict your fetch to a small batch size, say 10 objects. Then fetch 10 objects, display them, then fetch the next 10 and so on. This would keep your interface live and the user would understand they were watching a progressive process.

Use -[NSFetchRequest setFetchLimit:] to restrict the number of objects returned per fetch and -[NSFetchRequest setFetchOffset:] to index the subsequent fetches.

From the Apple Docs on fetchOffset:

The default value is 0.

This setting allows you to specify an offset at which rows will begin being returned. Effectively, the request will skip over the specified number of matching entries. For example, given a fetch which would normally return a, b, c, d, specifying an offset of 1 will return b, c, d, and an offset of 4 will return an empty array. Offsets are ignored in nested requests such as subqueries.

This can be used to restrict the working set of data. In combination with -fetchLimit, you can create a subrange of an arbitrary result set.

You might also want to look at your Core Data object graph design. 250 objects isn't a lot and there shouldn't be a significant performance hit processing that many objects. You may have to much data crammed into one entity so you have to fault in a lot unneeded data to get some relatively trivial information.

For example, a common mistake is to add an attribute with a great deal of data, such as an image, to a commonly accessed entity, such as Person entity. This causes problem because to get the Person.name attribute, you also have to load in an image of hundreds of kb.

A better design is to park large attributes in their own entity and link to other entities as relationships. That way, the large data chunk is only faulted in when you explicitly call the relationship. In the above example, you would put the image in its own entity. That way, when you wanted Person.name, you need only fault in the lightweight text.

TechZen
Thanks for the great answer TechZen, the 250 objects just contain string and number values. No images or anything like that. However, I did place some code within the `fetchRequest` method to place each returned object on the map. Maybe this is where the performance hit is
Griffo
Sounds like it is the 250 addAnnotation calls that is causing the issue. Try putting log statements in the method and find out where the bottleneck actually is and then code from there. The data you are pulling back should be near instantaneous with Core Data.
Marcus S. Zarra
I don't think you're doing this but you definitely do not want to fetch a single object, place that in the UI and then fetch the next single object. That would take forever even with a small number of objects because the fetch as to search the entire db each time for one object. Instead, perform a fetch of manageable size, display all the objects returned per fetch and then repeat as needed.
TechZen
No I'm fetching the results into an array like so `NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:nil];` then looping through the array, adding each to the map as I go
Griffo
That sounds right. I suggest you use Instruments, logs or pass through break points to see exactly where the app is spending all its time. It's almost certainly an accidental choke point and it's pointless to guess where it is. Just track it down and kill it.
TechZen