由于内存压力,从 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);
}
我正在查询 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);
}