A few beta testers will hopefully prove here shortly, but it looks like I've arrived at an initial solution. The problem is a couple of things, one being my lack of familiarity with CoreFoundation stuff, the other being a quirk with Exchange combined with the suckiness of plain C.
So on the CoreFoundation front, let's take a sample of code:
Code:
CFStringRef labelRef = ABMultiValueCopyLabelAtIndex(phoneMulti, i);
This function can return NULL. I guess that's not inherently an issue in and of itself, but unlike Obj-C which can send messages to nil all day long and not care, doing stuff with NULL in plain C is a big ole' load of hurt. Even something "simple" like memory management. This function has "copy" in the name, therefore it should be released later, which would look like this:
Code:
CFRelease(labelRef);
If the function returned NULL, this line will crash. So you have to do lots of protection checking:
Code:
if (labelRef != NULL)
CFRelease(labelRef);
And this is when you want to do unspeakable things to whoever at Apple decided not to provide an Obj-C interface to the address book.
As far as Exchange is concerned, we finally got our hands on an Exchange account, and this made it much easier to figure out what was going on. As stated previously, I'm fishing for emails and phone numbers, and trying to maintain any labels. If the address is a "home" email address in the address book, I would still like to identify it as a "home" email address within my app. So I do this:
Code:
ABMultiValueRef emailMulti = ABRecordCopyValue(personRef, kABPersonEmailProperty);
if (emailMulti != NULL) // <--- Woo, awesome!
{
for (int i = 0; i < ABMultiValueGetCount(emailMulti); i++)
{
CFStringRef labelRef = ABMultiValueCopyLabelAtIndex(emailMulti, i);
CFStringRef valueRef = ABMultiValueCopyValueAtIndex(emailMulti, i);
if (CFStringCompare(labelRef, kABHomeLabel, 0) == kCFCompareEqualTo);
...
The label is the type ("home"), the value is the value ("sample@sample.com"). There are standard constants for finding home, work, other, etc.
What seems to be happening is that when the account was configured to use Exchange, the email address came back without a label. labelRef is NULL. valueRef is
not NULL. Viewing the contact within the Contacts app revealed a visual clue to this. Instead of saying "Home", "Work", etc. it simply said "Email". You couldn't even tap on the cell to change it to something else. If you added another one, it also simply said "Email". And for whatever reason in this case, labelRef became NULL. I can only assume that Apple did not provide a constant for this case. And because it was NULL, the CFStringCompare() bombed hard.
So as a protective measure, I first added a test:
Code:
if ( (labelRef != NULL) && (valueRef != NULL) )
This prevented the app from crashing. However, I also wasn't grabbing addresses anymore, since this test failed. To cover the case described above, I added an additional test:
Code:
else if ( (labelRef == NULL) && (valueRef != NULL) )
In this case we are choosing to map this address to "Other".
I have no idea why we did not encounter some issue along these lines in a non-Exchange situation. I guess the standard address book is better about not returning NULL. At any rate, ignorance on my part + oddity on Apple's part = unhappy Brian (and client). I have no idea if this is common to Exchange accounts everywhere, or if there is some server-side setting that may or may not be flipped.
Since you can't have a value without a key in a NSDictionary, I'm sure that if there was an Obj-C API for the address book, someone would have figured this crap out sooner because they would have needed to provide a key of some kind even in a case like this. Oh well. Maybe in iOS 6.