-1

In a calendar app I display events based on the EventKit API. I fetch events from EKEventStore and display them in daily, weekly, monthly views, as lists, etc. Now I am running into some performance problems on iPhone 4.

The performance problems are mainly speed related. It takes several seconds, for example, to collapse or expand all table view sections (representing dates) to show the rows (representing events). It also takes 5-8 seconds to reload the table for the editing / export interface. I would have to check Instruments to give more details.

So far there have been no memory issues.

My strategy right now is to minimise the memory footprint. I am using arrays in memory, but they only contain the eventIdentifier, a short string. I can retrieve events with the EKEventStore method eventWithEventIdentifier:. I suspect that this is the reason for the performance hit.

Two alternatives come to mind:

  1. Use EKEvent objects instead of identifiers. However, I believe that this can be unpredictable regarding memory. Some events have lots of text so that the amount of data to be kept in memory is not limited. The duration of the period that has to show events could potentially also be very long.

  2. Port everything to Core Data, maybe with original EKEvent objects stored as transformable objects. This would be a major refactoring, but I could take advantage of NSFetchedResultsController and its optimisation features.

I have tried 1 and 2 - performance is still bad!

What is your experience? Have you seen performance issues with repeated calls to the EKEventStore database? What would be your advice?

UPDATE:

Instruments report that indeed the tableView's reloadData takes quite long (1.5 secs). I am not sure why because the state of the table view (collapsed sections or not) and the entire data are loaded before and that code is efficient.

I am not calculating any cell heights (sometimes this has been reported to force the entire table to load before display). The same lag appears when I call

[tableView beginUpdates];
[tableView endUpdates];

in order to animate the collapsing of the sections.

Note: maybe the topic of this question should be changed eliminating the EventKit part.

Mundi
  • 79,884
  • 17
  • 117
  • 140
  • Whatever you do, don't port to Core Data. The API provided is terrible at two way sync. You will not be able to efficiently hear about changes in the even store to update your CD. – Léo Natan Jul 27 '14 at 21:22
  • Not true. This is working perfectly. I am only having performance issues. – Mundi Jul 27 '14 at 21:23
  • From my experience, best is to perform the fetch from the event store on a background thread and once the data is ready, go back to UI thread. – Léo Natan Jul 27 '14 at 21:23
  • How are you listening to changes? If you kill the app, how do you know which events have changed in the mean time? – Léo Natan Jul 27 '14 at 21:24
  • Or are you working with an in-memory model? – Léo Natan Jul 27 '14 at 21:24
  • You didn't respond to my comments. – Léo Natan Jul 28 '14 at 22:24
  • What are the performance problems you are seeing? It takes too long, or takes too much memory? What do you see when you use the appropriate tool in Instruments? Can you update your question with that information? – quellish Jul 29 '14 at 02:00
  • Updated the question @LeoNatan: This would have generated too many comments. Please collect your thoughts and then ask pertinent questions. Core Data I tried both in memory and with SQLite store. Listening to changes via `EKEventStoreChangedNotification`. As stated, there are no update issues. – Mundi Jul 29 '14 at 08:02
  • Could you please add some more hints to the question? such as the number of events that you are fetching. I don't think that porting to Core Data will solve the issue. I've never seen performance issues in EventKit event on iPhone 4. Are you executing some operation on a background thread?Why don't you measure with time profiler? maybe is UI realted – Andrea Jul 29 '14 at 08:09
  • 2
    You've changed your question to say perf issues are with a change in a table view row state. U have not shown code, nor evidence that you have measured anything. You should be using Instruments to measure and find out what's taking time. If modifying a tableView and changing state of all rows, you're likely doing it wrong. There's no point in worrying about state of a row you cannot see and u should have no issue for a table that fits all rows on screen. Your expand /collapse state should not impact UI perf. It should probably be moved to the model layer, so u can lazily load rows as needed. – uchuugaka Jul 29 '14 at 08:14

2 Answers2

0

While I have never used EventKit, I can give you some suggestions:

  1. NSFetchedResultsController is a great thing, especially when your data is going to be changing after your table view has initially been loaded. The NSFetchedResultsController monitors for changes for your data and the NSFetchedResultsControllerDelegate protocol allows for incremental changes to your UITableView rather than reloading an entire UITableView when changes are made to the data that populates your table. In general, avoid doing a full reloadData on your UITableView when possible.

  2. If drawing your UITableView is really slow, chances are the source of the issue lays somewhere in the tableView:cellForRowAtIndexPath: method (almost guaranteed, although other culprits could be other UITableViewDataSource methods such as tableView:titleForHeaderInSection: or tableView:titleForFooterInSection:). If you are reading from disk, not using cached UITableViewCells, or doing heavy drawing in these methods, it could cause your UITableView to be extremely slow to reload / scroll.

  3. If you find that you are reading from the disk or performing some other slow operation in tableView:cellForRowAtIndexPath:, consider performing one read from the disk before your UITableView is drawn (i.e. in viewDidLoad:) and caching the results in an NSArray. Then your UITableViewDataSource methods can just read from the NSArray instead of the disk.

Michael Frederick
  • 16,664
  • 3
  • 43
  • 58
  • 1. I know. I am quite familiar with FRC. It did not work with the FRC, though. 2. This is not the scenario, i.e. no drawing, no fetching, proper recycling, etc. 3. Doing that in my original setup. – Mundi Aug 02 '14 at 18:44
  • "In general, avoid doing a full reloadData on your UITableView when possible." The exception to that rule is when you are changing a *lot* of items in the managed object at once, such as during a large import. In that case, `reloadData` is almost always more efficient than individual cell animations. – quellish Aug 02 '14 at 19:22
-1

The problem is your row animations and (probably) recalculating sections. This is pretty common, and is a UITableView thing rather than an EventKit or CoreData thing. More profiling should give you some ideas of how you can optimize your sections to be more performant.

Look at your implementation of the UITableViewDataSource methods that involve sections:

- numberOfSectionsInTableView:
- tableView:numberOfRowsInSection:
– tableView:sectionForSectionIndexTitle:atIndex:
– tableView:titleForHeaderInSection:
– tableView:titleForFooterInSection:

You are probably doing things in those methods that are relatively heavy. Maybe to build your sections you have to look at all of your data (this is common). If that's the case, come up with a reasonable strategy for caching the section information and invalidating it when the event store changes.

quellish
  • 21,123
  • 4
  • 76
  • 83
  • Should not a fetched results controller remediate any "look at all your data to build your sections"-problem? That did not help. – Mundi Aug 01 '14 at 20:43
  • Nope. A fetched results controller dies not solve this, and can make things worse. It still has to look at all of the data to build the sections, and it may rebuild all of the sections every time any object changes the section key path value. So if you are adding many objects, a full section rebuild every time. I can't make any more specific recommendations without knowing your application. If your data is always in a certain order you could implement pagination, for instance - this would not work for all applications. Changing how your sections are built is up to you. – quellish Aug 01 '14 at 21:15
  • What you write does not make sense. The data hardly changes at all, so the FRC should take care of optimizing its fetches etc, regardless of if it is retiring section or row info. – Mundi Aug 02 '14 at 07:52
  • The issue has little to do with fetches. It has to do with building the sections. To build the sections, the fetched results controller needs to access the section key path property value for all of the objects matching the fetch request. It has to do this each time the data changes - this is why using fetch limits, batch sizes, etc. doesn't have much effect when using sections - it still has to get all of the data, and fire a fault on each object. – quellish Aug 02 '14 at 17:01
  • It is frustrating because you are not reading my comments. I stated that the data *doesn't* change. There never was a question of a `fetchLimit`. And, no, a FRC does *not* have to get "all the data" when calculating a section. – Mundi Aug 02 '14 at 17:20
  • "I stated that the data doesn't change.". No, you said it "hardly changes it all". And yes, a fetched results controller needs the property value of the key path specified in `sectionNameKeyPath` for each managed object matching the fetch request to build the `NSFetchedResultsSectionInfo` objects. The mention of fetch limits and batches was in response to your statement "the FRC should take care of optimizing its fetches". Those things would be how it would "optimize" it's fetching behavior. What does instruments tell you about the cache miss and faulting behavior? – quellish Aug 02 '14 at 19:19
  • And again, your question (as it currently is) and this answer have nothing to do with NSFetchedResultsController. – quellish Aug 02 '14 at 19:25
  • The focus of questions/answers can change in the course of iterative discussion. – Mundi Aug 03 '14 at 11:41