由于内存压力,从ABAddressBook获取所有图像崩溃

时间:2015-04-27 14:32:05

标签: ios objective-c contacts addressbook abaddressbook

我正在查询IOS地址簿中的所有人,并将他们的图像存储在本地缓存中。一切都适用于小型地址簿 - 但由于内存压力,很多条目(> 1000)会使应用程序崩溃

在调查问题之后,似乎ABPersonCopyImageData为该图像分配内存,并返回引用数为2的CFDataRef photoData。在释放数据CFRelease(photoData)后,引用数保持为1 ,这表明ABAddressBookRef addressBook保留了引用,可能是出于缓存的原因。内存消耗在整个循环中线性增加。

循环CFRelease(addressBook)最终清理所有引用并释放内存。因此,一个黑客解决方案是定期发布地址簿并创建一个新的地址簿(每100项左右),但它有一些缺点。

是否有其他方法可以告诉地址簿释放对图像数据的引用?

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

2 个答案:

答案 0 :(得分:0)

我认为@autoreleasepool确实会像Volker在评论中所说的那样帮助你的案子。 调用CFRelease实际上并不释放内存,它只会减少对象的保留计数,就像release在ARC之前一样。 因此,内存使用量会累积到自动释放池的下一次排放,这会在当前运行循环的当前周期结束时发生。

此外,请注意您可以使用免费桥接将所有权转移到ARC,从而提高效率:

- (void)testContacts {
    ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, nil);
    NSArray *allContacts = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople(addressBook);
    CFIndex nPeople = ABAddressBookGetPersonCount(addressBook);

    for (CFIndex idx = 0; idx < nPeople; idx++ ) {
        @autoreleasepool {
            ABRecordRef person = (__bridge ABRecordRef)allContacts[idx];

            if (ABPersonHasImageData(person)) {
                NSData *photoData = (__bridge_transfer NSData*)ABPersonCopyImageDataWithFormat(person, kABPersonImageFormatThumbnail);
                if (photoData) {
                    // do something (eg. store data) - does not affect problem
                }
            }
        }
    }

    CFRelease(addressBook);
}

答案 1 :(得分:0)

好的,到目前为止定期发布addressBook似乎是解决问题的唯一方法,请参阅下面的代码。

每100个条目发布会增加 8%的执行时间开销,但正如预期的那样会使内存几乎减少10倍 1000个电话簿条目(即35MB和在iPhone 4上15秒对4MB对比16.2秒

我希望任何人都可以提出更好的解决方案,直到那时我才会使用它。

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