I'm querying all persons from the IOS address book and store their image in a local cache. Everything works fine for small address books - however a lot of entries (>1000) crash the app due to memory pressure.
After investigating the issue it seems that the ABPersonCopyImageData
allocates memory for that image, and returns a CFDataRef photoData
with a refcount of 2. After releasing the data CFRelease(photoData)
the refcount stays at 1, which suggests that the ABAddressBookRef addressBook
keeps a reference, probably for caching reasons. The memory consumption linearly increases through the whole loop.
After the loop CFRelease(addressBook)
finally cleans up all references and frees up the memory. So one hack-ish solution is to periodically release the address book and create a new one (every 100 items or so), but it has some downsides.
Is there another way to tell the address book to release the reference to the image data?
- (void)testContacts {
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, nil);
CFArrayRef allContacts = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex nPeople = ABAddressBookGetPersonCount(addressBook);
for (CFIndex idx = 0; idx < nPeople; idx++ ) {
ABRecordRef person = CFArrayGetValueAtIndex( allContacts, idx );
if (ABPersonHasImageData(person)) {
CFDataRef photoData = ABPersonCopyImageDataWithFormat(person, kABPersonImageFormatThumbnail);
if (photoData) {
// do something (eg. store data) - does not affect problem
CFRelease(photoData);
}
}
}
CFRelease(addressBook);
}
Ok, so far periodically releasing the addressBook
seems to be the only solution to problem, see code below.
Releasing every 100 entries adds around 8% overhead on execution time, but as expected reduces the memory almost by a factor of 10 for 1000 phonebook entries (i.e 35MB and 15sec vs 4MB vs 16.2sec on an iPhone 4)
I hope anybody can come up with a better solution, until then I will use this.
void abImagesV3() {
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, nil);
CFArrayRef allContacts = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex nPeople = ABAddressBookGetPersonCount(addressBook);
for (CFIndex idx = 0; idx < nPeople; idx++ ) {
ABRecordRef person = CFArrayGetValueAtIndex( allContacts, idx );
if (ABPersonHasImageData(person)) {
CFDataRef photoData = ABPersonCopyImageDataWithFormat(person, kABPersonImageFormatThumbnail);
if (photoData) {
// do something (eg. store data) - does not affect problem
CFRelease(photoData);
}
}
if (idx%100 == 99) {
CFRelease(addressBook);
CFRelease(allContacts);
addressBook = ABAddressBookCreateWithOptions(NULL, nil);
allContacts = ABAddressBookCopyArrayOfAllPeople(addressBook);
}
}
CFRelease(addressBook);
}