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

Fetching all images from ABAddressBook crashes due to memory pressure

我正在查询 IOS 地址簿中的所有人,并将他们的图像存储在本地缓存中。对于小型地址簿一切正常 - 但是 大量条目 (>1000) 由于内存压力导致应用程序崩溃

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

循环后CFRelease(addressBook)终于清理了所有的引用,释放了内存。因此,一种 hack-ish 解决方案是定期发布地址簿并创建一个新地址簿(每 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);
}

我认为 @autoreleasepool 确实会像 Volker 在评论中建议的那样帮助您解决问题。 调用 CFRelease 实际上并没有释放内存,它只是减少了对象的保留计数,就像 release 在 ARC 之前所做的那样。 因此,内存使用量会累积到自动释放池的下一次耗尽,这发生在当前 运行-loop 的当前循环结束时。

此外,请注意,您可以使用免费桥接将所有权转让给 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);
}

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

每 100 个条目释放一次会增加大约 8% 的执行时间开销,但正如预期的那样将内存减少几乎 10 倍对于 1000 个电话簿条目(即 iPhone 4 上的 35MB 和 15sec vs 4MB vs 16.2sec)

我希望任何人都能提出更好的解决方案,在那之前我会使用它。

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