I'm creating an iPhone app that will pull data down from a Web API, including email addresses. I'd like to display an image associated with each email address in table cells, so I'm searching the Address Book for images and falling back on a default if the email address isn't in the book. This works great, but I have a few concerns:
Performance: The recipes I've found for looking for an address book record by Email address (or phone number) are reportedly rather slow. The reason for this is that one must iterate over every address book record, and for each one that has an image, iterate over all email addresses to find a match. This can be time-consuming for a large address book, of course.
Table Cells: So I thought I'd gather up all the email addresses for which I need to find images and find them all at once. This way I iterate through the book only once for all addresses. But this doesn't work well for table cells, where each cell corresponds to a single email address. I'd either have to gather all the images before displaying any cells (potentially slow), or have each cell look up each image as it loads (even slower, as I'd need to iterate through the book to find a match for each email address).
Asynchronous Lookup: So then I thought I'd look them up in bulk, but asynchronously, using NSInvocationOperation
. For each image found in AddressBook, I'd save a thumbnail in the app sandbox. Then each cell could just reference this file and show the default if it doesn't exist (because it's not in the book or hasn't yet been found). If the image is later found in the asynchronous lookup, the next time the image needs to be displayed it would suddenly appear. This might work well for periodic regeneration of images (for when images have been changed in the address book, for example). But then for any given instance of my app, an image may not actually show up for a while.
Asynchronous Table Cell Lookup: Ideally, I'd use something like markjnet's asynchronous table cell updating to update table cells with an image once it has been downloaded. But for this to work, I'd have to spin off an NSInvocationOperation
job for each cell as it's displayed and if the cached icon is missing from the sandbox. But then we're back to inefficiently iterating through the entire address book for each one—and that can be a lot of them if you've just downloaded a whole bunch of new email addresses.
So my question is: How do others do this? I was fiddling with Tweetie2, and it looks like it updates displayed table cells asynchronously. I assume it's sending a separate HTTP request for every image it needs. If so, I imagine that searching the local address book by email address isn't any less efficient, so maybe that's the best approach? Just not worry about the performance issues associated with searching the address book?
If so, is saving a thumbnail image in the sandbox the best approach to caching? And if I wanted to create a new job to update all the thumbnails with any changes in the address book say once a day, what's the best approach to doing so?
How do the rest of you solve this sort of problem? Suggestions would be much appreciated!
Regardless of what strategy you use for the actual caching of images, I would only make one pass through the Address Book data each time you get a batch of email addresses, if possible. (And yes, I would do this asynchronously.)
Create an NSMutableDictionary which will serve as your in-memory cache for search results. Initialize this dictionary with each email address from the download as a key, with a sentinel as that key's value (such as [NSNull null]
).
Next, iterate through each ABRecordRef in the Address Book, calling ABRecordCopyValue(record, kABPersonEmailProperty)
and looping through the results in each ABMultiValue that is returned. If any of the email addresses are keys in your cache, set [NSNumber numberWithInt:ABRecordGetRecordId(record)]
as the value of that key in your dictionary.
Using this dictionary as a lookup index, you can quickly obtain the images of ABRecordRefs for only the email addresses that you are currently displaying in your table view given the user's current scroll position, as suggested in hoopjones's answer. You can add an address book change listener to invalidate your cache, trigger another indexing operation, and then update the view, if your application needs that level of "up-to-date-ness".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With