Search code examples
iosobjective-ccontactsaddressbookabaddressbook

Fetching all images from ABAddressBook crashes due to memory pressure


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);
}

Solution

  • 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);
    }