9

We've recently converted our code to use UIDocument instead of manipulating files on the file system directly, and we've encountered some performance issues as a result. We are wondering whether we are using this class incorrectly, if anyone else had these issues, and what are the common ways to address them.

Our app

We have a "shoebox app" that manages a bunch of documents, each consisting of multiple image files that can be quite heavy, a small metadata file and a small preview image. The user may have many documents on her device (1000+ documents). Each document's files are grouped in an directory and we use NSFileWrapper to read and write them.

When our app starts, it needs the metadata of all the documents in order to show a document index, and a subset of the preview images. More preview images are loaded as the user scrolls.

In order to get that information, we open all the documents, read their metadata and preview image if we need to, close them, and then open again on demand.

Problem #1: Slow loading time

It takes a lot of time to open all the documents and read their metadata. I think there are several factors contributing to this problem: - Each document open action is relatively slow - The open document blocks and the completion blocks are executed on the same queue, which makes the operation's latency very bad (my document is open, but the completion block has to wait for X open document blocks before it can run)

We thought about solving this problem using a separate index file, but this approach has the drawback that we will need to manage the metadata in two places and that we will need to keep it synched with the file system in case iCloud changes the files.

Problem #2: Threading

Each open document creates its own "File Access Thread". When we open many documents concurrently, the overhead crushes the app.

We solved this issue by synching the open operations using a semaphore. This approach has the drawback that it slows down the loading even more.

Questions

  1. Is there some fundamental problem with the way we are using UIDocument? If not:
  2. Has anyone encountered a similar loading time problem? What is the common way to solve it? Do other apps keep an index file?
  3. Is there a way to make UI document use a thread pool? If not, how do you control resource usage?

Thanks!

Hila
  • 1,080
  • 1
  • 9
  • 22
  • Did you ever find a solution? I'm running into the same problems with a lot of UIDocuments that contain several large video files. I'm thinking about ditching UIDocument altogether or keeping a separate index file. Maybe you acquired some insights on this? – Rengers Jun 05 '14 at 18:44
  • @Rengers Please see my answer below. Unfortunately, I have no good news for you. – Hila Jun 05 '14 at 20:22

3 Answers3

9

Ok, no good news here.

We tried consulting with friends in the industry, profiling UIDocument and using modified implementations of it that alter various aspects of its operation in order to see if we can improve its performance but to no avail.

My conclusion is that UIDocument is not suitable for this kind of usage - it is just not designed to support the latency and throughput requirements we have for open operations. UIDocument should only be used when you want to open a small number of files at any given moment (much like word processors and drawing apps). Admittedly, this is exactly what Apple's documentation says, but I guess we had to learn just how serious they were the hard way :)

We ended up using some "tricks" to improve the user experience, and will move away from UIDocument as soon as we can.

So my recommendation is that only if:

  1. Your app resembles a document based app in nature, meaning you will not have more than a few documents open at any given moment
  2. You do not need the information inside the documents in order to "discover" them and show them to the user, or can afford the overhead of managing a separate index file
  3. You really need the auto saving/undo/synchronization/iCloud abilities of this class

then use it. Otherwise consider other solutions.

A a side note which is not directly related to the question but I will add here as a public service: UIDocument's async model required some major changes in the app architecture when we moved from direct file access. If you plan on making this move, evaluate the work you will need to do carefully.

Good luck future programmers.

Hila
  • 1,080
  • 1
  • 9
  • 22
  • 1
    Hila, thanks for this extended follow up! I've been profiling the saving and loading using NSFileWrappers. It seems NSFileWrapper does really load all the data into RAM, which makes it unusable for large files. I'm getting more convinced now to ditch UIDocument en devise a custom solution that supports metadata and large files. Another option I though of was overriding `saveToURL:forSaveOperation:completionHandler`. Did your optimizations happen to include this? – Rengers Jun 05 '14 at 20:36
  • 1
    @Rengers We also suspected NSFileWrapper, but as far as I know it does memory mapping which shouldn't be as bad as reading all the data into memory (I may be wrong, though). We tested its effect by replacing our UIDocument base class with a modified class that uses an implementation that reads data on demand, but it didn't improve our performance by much. We also created a test application that opened a bunch of really small files, and the performance was poor as well. I don't remember which methods we tried to modify. – Hila Jun 05 '14 at 21:04
  • @Rengers I suspect that the threading model is the culprit, but I didn't have time to verify it because at some point we had to cut our loses and move on. Hope this helps :) – Hila Jun 05 '14 at 21:04
  • It sure helps, looks like I'm in the same boat you guys were some time ago :). I'll probably switch to a custom design after hearing this. Saving really large files on iCloud isn't really feasible anyway, and separate metadata keeping kinda defeats the pros of self-contained documents. Thanks again! – Rengers Jun 05 '14 at 21:28
0

The document classes have methods to read asynchronously. Have your tried that?

Joel
  • 2,285
  • 2
  • 21
  • 22
0

This really sounds like something more suited to Core Data or SQLite, for the metadata. At the very least, cache the metadata in Core Data (a single store for the entire app, not one per document).

Rick
  • 3,298
  • 3
  • 29
  • 47