写在前面
该篇文章主要记录日常 iOS 开发过程中遇到的报错或者是调试心得。
目录
[ImageManager] FigPhotoDecompressionContainerDecodeImageForIndexAsync -[PLFigPreheatItem startPreheatRequestWithCompletionHandler:]_block_invoke_2 returned -16991 from /var/mobile/Media/PhotoData/Thumbnails/V2/DCIM/112APPLE/IMG_2270.JPG/5005.JPG
具体报错问题
1、获取相册所有图片引起的内存暴增问题【2019.09.20】
日志打印:
备注:会重复打印多次该日志,直至系统因为内存暴增而 crash
[ImageManager] FigPhotoDecompressionContainerDecodeImageForIndexAsync -[PLFigPreheatItem startPreheatRequestWithCompletionHandler:]_block_invoke_2 returned -16991 from /var/mobile/Media/PhotoData/Thumbnails/V2/DCIM/112APPLE/IMG_2270.JPG/5005.JPG
问题描述:
要求是要获取相册中所有的图片数据进行展示。初始的写法是优先获取相册中的所有
PHAsset数据对象,然后再通过遍历获取对应的缩略图,并切换到主线程进行展示。但是因为图片数量过多(当前测试为 14000 张图片),引起了内存暴增,最终 crash。
iPhoneX iOS12.1 环境下
初始代码如下:
/** 获取相册全部原图(PHAsset) **/
+ (NSArray *)getAllLocalOriginalImages {
NSMutableArray *tempImages = [NSMutableArray array];
// 获取所有智能相册
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
for (NSInteger i=0; i<smartAlbums.count; i++) {
// 是否按创建时间排序
PHFetchOptions *option = [[PHFetchOptions alloc] init];
option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeImage];
PHAssetCollection *assetCollection = smartAlbums[i];
// 遍历获取相册
if (assetCollection.assetCollectionSubtype == PHAssetCollectionSubtypeSmartAlbumUserLibrary) {
PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:nil];
NSArray *assets;
if (fetchResult.count > 0) {
// 某个相册里面的所有 PHAsset 对象
assets = [self getAllPhotosAssetInAblumCollection:assetCollection ascending:NO];
[tempImages addObjectsFromArray:assets];
}
}
}
return tempImages;
}
#pragma mark - 根据 PHAsset 获取图片信息
/** 根据 PHAsset 获取图片信息 **/
+ (UIImage *)getImage:(PHAsset *)phAsset targetSize:(CGSize)targetSize {
__block UIImage *image = nil;
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.networkAccessAllowed = YES;
options.synchronous = YES;
// 设置 resizeMode = PHImageRequestOptionsResizeModeExact, 否则返回的图片大小不一定是设置的 targetSize
options.resizeMode = PHImageRequestOptionsResizeModeExact;
// 设置 deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat,是保证 resultHandler 值回调一次,否则可能回回调多次,只有最后一次返回的图片大小等于设置的 targetSize
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
PHImageManager *manager = [PHImageManager defaultManager];
[manager requestImageForAsset:phAsset targetSize:targetSize contentMode:PHImageContentModeDefault options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
image = result;
}];
return image;
}
错误代码:
#pragma mark - 获取图片数据源
/** 获取图片数据源 **/
- (void)getAllImages {
dispatch_async(dispatch_queue_create("GetAllImages_Queue", DISPATCH_QUEUE_CONCURRENT), ^{
/** 获取当前本地所有图片 **/
self.imageDataArrayM = [NSMutableArray array];
self.tempArrayM = [NSMutableArray arrayWithArray:[LHAPI getAllLocalOriginalImages]];
CGSize targetSize = CGSizeMake(self.imageTargetW, self.imageTargetW);
for (NSInteger i=0; i<self.tempArrayM.count; i++) {
PHAsset *asset = self.tempArrayM[i];
if (asset.pixelWidth < self.imageTargetW && asset.pixelHeight < self.imageTargetW) {
targetSize = CGSizeMake(asset.pixelWidth, asset.pixelHeight);
}
LHLocalUploadModel *model = [[LHLocalUploadModel alloc] init];
model.isVideo = NO;
model.asset = asset;
model.image = [LHAPI getImage:asset targetSize:targetSize];
model.originIndex = i;
model.selectIndex = 0;
[self.imageDataArrayM addObject:model];
// 让其添加到 第30个时就优先展示
if (self.imageDataArrayM.count % self.firstShowNum == 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.imageCollectionView reloadData];
});
}
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"----------- 相册图片,共 %ld 张照片", self.imageDataArrayM.count);
[self.imageCollectionView reloadData];
});
});
}
问题原因:
- (PHImageRequestID)requestImageForAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler;该方法在执行的时候会占用大量内存;而
for循环遍历了太多的数据,所以会引起内存暴增;
可以通过分页拉取图片数据,进而缓解内存暴增问题。
> 解决办法:
> 降低缩略图尺寸(即图片质量)、分页遍历图片数据,单次遍历数据量少量即可。
> 参考链接: > [PHImageFileURLKey missing from .png images in iOS 12.1](https://forums.developer.apple.com/thread/110721)
> 解决后的代码如下:
#pragma mark - 获取图片数据源
/** 获取图片数据源 **/
- (void)getAllImages {
dispatch_async(dispatch_queue_create("GetAllImages_Queue", DISPATCH_QUEUE_CONCURRENT), ^{
/** 获取当前本地所有图片 **/
self.imageDataArrayM = [NSMutableArray array];
self.imageDataOriginArrayM = [NSMutableArray arrayWithArray:[LHAPI getAllLocalOriginalImages]];
self.imagePageNum = 0;
self.imagePageSize = 1000;
[self reloadNewImageData];
});
}
/** 当快滚动到底部时拉取新的数据 **/
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.imageCollectionView) {
CGFloat offsetY = self.imageCollectionView.contentOffset.y;
CGFloat itemH = self.collectionItemW + 5.0f;
NSInteger curIndex = (offsetY / itemH) * 4;
if (curIndex > 0 && curIndex > self.imageDataArrayM.count - 50) {
NSLog(@"curIndex ----------- %ld", curIndex);
[self reloadNewImageData];
}
}
}
/** 获取更多数据源 **/
- (void)reloadNewImageData {
if (self.imageDataArrayM.count == self.imageDataOriginArrayM.count) {
return;
}
self.imagePageNum++;
NSInteger curPageIndex = self.imageDataArrayM.count;
NSInteger nextPageTotal = self.imageDataArrayM.count + self.imagePageNum * self.imagePageSize;
nextPageTotal = nextPageTotal <= self.imageDataOriginArrayM.count ? nextPageTotal:self.imageDataOriginArrayM.count;
CGSize targetSize = CGSizeMake(self.imageTargetW, self.imageTargetW);
for (NSInteger i=curPageIndex; i<nextPageTotal; i++) {
PHAsset *asset = self.imageDataOriginArrayM[i];
if (asset.pixelWidth < self.imageTargetW && asset.pixelHeight < self.imageTargetW) {
targetSize = CGSizeMake(asset.pixelWidth, asset.pixelHeight);
}
LHLocalUploadModel *model = [[LHLocalUploadModel alloc] init];
model.isVideo = NO;
model.asset = asset;
model.image = [LHAPI getImage:asset targetSize:targetSize];
model.originIndex = i;
model.selectIndex = 0;
[self.imageDataArrayM addObject:model];
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"----------- 相册图片,共 %ld 张照片", self.imageDataArrayM.count);
[self.imageCollectionView reloadData];
});
}