最近遇到一个保存图片时的崩溃, 崩溃信息如下:
0 CoreFoundation!__exceptionPreprocess + 0x84
1 libobjc.A.dylib!objc_exception_throw + 0x38
2 CoreFoundation!+[NSException raise:format:] + 0x7c
3 Foundation!_NSMutableDataGrowBytes + 0x300
4 Foundation!-[NSConcreteMutableData appendBytes:length:] + 0x174
5 ImageIO!CGImageWriteSessionPutBytes + 0x94
6 ImageIO!write_fn + 0x28
7 ImageIO!png_write_chunk_data + 0x34
8 ImageIO!_cg_png_write_complete_chunk + 0x40
9 ImageIO!png_compress_IDAT + 0x160
10 ImageIO!png_write_find_filter + 0xa80
11 ImageIO!_cg_png_write_row + 0x300
12 ImageIO!writeOnePng + 0x1358
13 ImageIO!_CGImagePluginWritePNG + 0x90
14 ImageIO!CGImageDestinationFinalize + 0x1e8
15 UIKit!UIImagePNGRepresentation + 0x248
16 MFAFImageCache addImage:forRequest:withAdditionalIdentifier:
提示的异常信息是:
Exception Name: NSMallocException
Exception Reason: *** -[NSConcreteMutableData appendBytes:length:]: unable to allocate memory for length (1751849)
查看一下调用函数, 主要调用如下:
NSString *filePath = [FileModel getLocalFileName:kPhotosDirectory byUrl:[[request URL] absoluteString]];
if (image) {
@try {
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:filePath atomically:YES];
} @catch (NSException *exception) {
MFLogError(@"ImageCache", @"save image error:%@", exception);
} @finally {
}
}
说明在执行 NSData *imageData = UIImagePNGRepresentation(image)
这个操作时,崩溃了。而且崩溃的原因是内存分配出错。 通常这种问题都是因为内存不足引起的。但是查看了一下log, 发现并没有 MemoryWarning 这句日志,说明程序还没来得及处理系统的内存警告就崩溃了。
根据这些现象猜测可能的原因
- 图片比较大
- 外层也许有个for循环,多次调用,导致临时对象没来得及马上释放
针对这两种猜测,作了相应的处理
图片内容较大
其实有 UIImage 对象的时候,说明图片已经可以正常展示,加载这张图片到内存暂时是没有问题的。但保存的时候又根据 UIImage 对象生成 NSData, 然后在通过NSData保存文件。 在NSData写入文件到释放前,内存里面其实有两份 图片 的数据, 一份是显示的, 一份是准备用来保存的NSData。 这样其实就是临时分配了一块大空间,如果可以减少临时对象的分配,直接读取UIImage的数据来存储就可以降低内存的使用。
通过查阅文档,发现Image IO 的库可以做到。
于是,保存函数改成这样:
+ (BOOL)saveImage:(UIImage *)image toFile:(NSString *)filePath
{
if (!image.CGImage) {
return NO;
}
CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:filePath];
CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, nil);
if (!destination) {
return NO;
}
CGImageDestinationAddImage(destination, image.CGImage, nil);
CGImageDestinationFinalize(destination);
CFRelease(destination);
return YES;
}
临时对象没有马上释放
这种情况比较好处理,就是因为加到 AutoReleasePool 的对象没有马上释放,必须要等下一次 RunLoop 循环才会清理。
这种问题可以加 @autoreleasepool{} 处理。
最后函数变成:
+ (BOOL)saveImage:(UIImage *)image toFile:(NSString *)filePath
{
if (!image.CGImage) {
return NO;
}
@autoreleasepool {
CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:filePath];
CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, nil);
if (!destination) {
return NO;
}
CGImageDestinationAddImage(destination, image.CGImage, nil);
CGImageDestinationFinalize(destination);
CFRelease(destination);
}
return YES;
}
当然 @autoreleasepool 写在这里作用并不大, 应该要加在相应的for()循环里面。
因此定义一个宏,方便替换,只要把原来的 for 替换成 AutoRelease_for 就可以。
#define AutoRelease_for(...) for(__VA_ARGS__) @autoreleasepool
网友评论