UIImagePNGRepresentation 保存图片时Crash

最近遇到一个保存图片时的崩溃, 崩溃信息如下:

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 
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,370评论 30 472
  • 一、深复制和浅复制的区别? 1、浅复制:只是复制了指向对象的指针,即两个指针指向同一块内存单元!而不复制指向对象的...
    iOS_Alex阅读 5,295评论 1 27
  • 1.1 什么是自动引用计数 概念:在 LLVM 编译器中设置 ARC(Automaitc Reference Co...
    __silhouette阅读 10,683评论 1 17
  • 父类实现深拷贝时,子类如何实现深度拷贝。父类没有实现深拷贝时,子类如何实现深度拷贝。• 深拷贝同浅拷贝的区别:浅拷...
    JonesCxy阅读 4,758评论 1 7
  • 今天为了我的剧,我终于去开始找有偿的小伙伴了。心里有点小难过,感觉自己违背了初心。 我有一部个人同人广播剧要做,那...
    苍峰冷冽阅读 3,613评论 0 3