AddressBook record identifiers on Mac and iOS
ProfitTrain has a cool feature which lets users link business and client contact info to cards in the system AddressBook. When linked the normal contact fields become disabled and ProfitTrain pulls in changes from the AddressBook as they happen; simplifying the user’s need to edit data in multiple places and arguably living up the original goal of the AddressBook API. Over time however, with the addition of MobileMe, iCloud and the iPhone, things are breaking.
To implement it’s AddressBook “link” functionality ProfitTrain for Mac OS X uses the ABRecord’s uniqueId which itself is a wrapper for the C level kABUIDProperty. kABUIDProperty is documented with the following:
The unique ID for this record. It’s guaranteed never to change, no matter how much the record changes. If you need to store a reference to a record, use this value.
Maybe in the past this was true but I’m not so sure these days. For example, with the release of iCloud I received emails from some ProfitTrain users who were getting warnings that the link between clients and the AddressBook cards were breaking. Turns out when enabling iCloud your AddressBook records get all new uniqueId values. This isn’t even limited to the initial setup. Turn iCloud contact syncing off and on and it’ll do it again.
Things are fragile on the iOS side as well. iOS only has a C level API and doesn’t implement uniqueId but instead provide a function called ABRecordGetRecordID. ABRecordID at the end of the day ends up being a int32_t and obviously doesn’t match uniqueId which has always been a string in a format like this:
725D14DB-2F63-40FD-B1EB-ED696C13E03B:ABPerson
The attribute documentation for ABRecordID suggests it is a “unique ID” but to get the full story you need to read the Address Book Programming Guide for iOS. I’ll quote the relevant notes (with some additions in parentheses) below:
Every record in the (iOS) Address Book database has a unique record identifier. This identifier always refers to the same record, unless that record is deleted or the MobileMe (or iCloud) sync data is reset. Record identifiers can be safely passed between threads. They are not guaranteed to remain the same across devices.
The recommended way to keep a long-term reference to a particular record is to store the first and last name, or a hash of the first and last name, in addition to the identifier. When you look up a record by ID, compare the record’s name to your stored name. If they don’t match, use the stored name to find the record, and store the new ID for the record.
My dream of storing a permanent and unique identifier for ABRecord objects across a set of Mac/iOS apps, syncing their data in the iCloud, is broken. Hell, even if you are contempt to a single platform these things will change under your feet.
If I want to continue to “link” ProfitTrain companies to cards in the AddressBook it’s come down to name matching which is really disappointing.
Talking with some friends who happen to work on AddressBook they’ve said this behavior wasn’t something they did intentionally or haphazardly. I responded back that I understand they are solving complex problems over there but me and the AddressBook API, we had a deal and you broke our deal.
Before reading the Address Book Programming Guide for iOS I complained this topic wasn’t properly documented. I’d still argue the Mac OS X variant could use some improvements, including the removal of that “never to change” stuff but hopefully this blog post helps some lucky Google searcher out there. For now I’ll be writing up some new code to match on names. **shudder**
Posted on: November 7, 2011 – 7:32 PM

11 Comments
Thank you! Very useful indeed! Though there is one pitfall I think - what’s in the case if You have let’s say -10 entries with John Smith? Name matching will be broken as well..
I was actually just about to add some functionality to my app that assumed the uniqueness of the record ID (as guaranteed by the documents)
:(
Is there any documentation that explains when and how the ID can change out from under you?
I thank Mike for saving me a good deal of trouble trying to figure out why the Address Book unique ids my program stored suddenly started dereferencing to null. I did some digging into this, and report what I found.
I found in ~/Library/Application Support/AddressBook a file named “Migration 20111013094925-331.abbu.tbz”. When unzipped, this generates an abbu bundle, which contains a Metadata folder, which has all the AddressBook records that my program is looking for, that is, “.abcdp” records that have the original unique ids. For instance, this abbu bundle has a record for Mr X, with unique id (UID) 034B2647-2824-4344-9A14-D707EC73E760:ABPerson, with 5 keys. This migration is, I think, triggered when a user turns on iCloud syncing.
The support folder has its own Metadata folder filled with abcdp files. Mr. X’s file in this folder has 5 keys, and a UID property with a new value of 595AD78A-CBB7-44C7-9902-03D4CBF4B096:ABPerson. This is the record that shows up in the AddressBook application in the group On My Mac. The migration apparently created a new record from the original On My Mac group, populated mostly from the original record, but changing the unique id. Changing the unique id seems gratuitous. Why couldn’t the migration have preserved the original unique id?
Also in the AddressBook support folder is a folder named Sources, which contains two folders with long random names, one of which defines my Exchange server, and the second of which has a Metadata subfolder with another set of abcdp records. These are, I think, the iCloud records. Here Mr X’s record has 11 keys, now including 2 unique ids! There is the UID property, with a new value, F5956398-76BD-4DFF-8672-993E6E654A9B:ABPerson, and a com.apple.uuid, with a value of 692D8B5B-E1C1-46E3-9C9D-CD85126296A3.
Is any of this helpful? Not much. One could, in a few desperate cases, use the migration archive to find a record for an obsolete UID, and extract properties from the old record to find the person in the migrated database. But it’s hard to see how this could be scaled to a workable fix for all the broken records in a program widely distributed.
The suggestion to use first and last names as a proxy for a UID is not satisfactory. It’s not just that names are not unique. Names change. For instance, people commonly change their last names when they marry or divorce. Phone numbers change; email addresses change. There is no substitute for an immutable unique id. Changing the UID is certainly a serious bug which makes the AddressBook API pretty much useless.
Just to add to the craziness of the situation, I looked into the matter of a custom property I implemented in my program. The value of this property was the UID of another ABPerson, which allowed me to link records into households; it served as a spouse link. Much to my surprise, migration changed the value of my custom property, but not to the new UID of the spouse. The new value is not the UID of any record in either my original database, the migrated On My Mac group, or the iCloud group. As far as I can tell, it’s just a random identifier belonging no record. I can only speculate that migration recognized that my custom property was an AB UID because it ended in “ABPerson”, and so changed it, without taking any particular care to see to it that the new value referenced an actual record.
Nonetheless, the fact that migration propagated the property, even if it didn’t preserve the value, suggests that perhaps a custom unique id which didn’t look like an official UID could survive migration, and provide us the immutable identifier that we need.
@Tim: Thanks for the additional notes and suggestion of using a custom property. I’ll look into it.
The more I have thought about the new situation, the more problems I see.
We used to have a single address database. Now any iCloud subscriber will have 2, one On My Mac and the other on the cloud. The well-informed user will turn off the OMM contacts, and update only the cloud records. The less informed user will be perplexed by the fact that after joining the cloud, he has duplicates for every record, and will update records in one set or the other haphazardly. If my app uses the AB API, then it has to decide if it will take UIDs from OMM or iCloud, or just take whatever the whim of the user happens to drop into the app. Once the app has a UID for person A, say from OMM, the user updates the record in the cloud, since Jane White married Joe Black, and now calls herself Jane White-Black. The next time the user opens my app, he can’t understand why Jane is still Jane White. “But I just updated the record? Why isn’t the update showing?”
Even if I have a custom property, do I add that custom property to the OMM record, or the cloud record, or both?
I can’t see any way to recover from this blunder on the part of the Address Book team. There was a perfectly good UID that was useful to developers. This has been hopelessly broken, apparently under the assumption that the AddressBook app is the only client of the AB database.
Increasingly it seems to me that the best solution is for the app to keep its own contact database.
this is difficult news. i don’t like to think of apple breaking this promised never-changing UID value without telling developers, and changing the docs.
i hope you filed a radar on it.
also, i wonder why i’m not able to find others mentioning this ?
i was thinking that there could be more devs being hit with this now that users are moving to icloud.
thanks for putting this out there.
@jim: I think most apps use the address book api for short term relationships with the card, to send a email or file right now. The ones who are trying to do long term relationships with the card are having similar issues. I’ve head backchannel confirmation they “had this problem to” and are trying to work around it best they can.
How about creation date? Does it get preserved through iCloudifications? (Understanding that it’s not perfect.)
Looks like Apple solved this for themselves somehow: http://help.filemaker.com/app/answers/detail/a_id/9992/~/important%3A-bento-4-and-icloud
What I suspect Bento did was add their own custom ID to the ABRecord. You can add your own values. Apple’s address book app will ignore your custom value, but they are stored in the address book database. ProfitTrain could do the same.
I don’t see which API on iOS can be used to store “custom” properties? Any hints?
Post a Comment | Comment RSS feed