问题:
即时通讯App在发送图片消息时内存暴涨导致网络请求初始化失败(内存不足OOM),发送消息失败。
可能原因分析:
- 有内存泄漏。
- 发送的图片消息,可能包含大图,没有进行压缩处理,导致内存占用过高。
- 在发送过程中,可能同时进行了图片的读取和处理,如果图片很大,处理过程中会产生很大的内存峰值。
解决方案:
- 对于发送消息时的内存暴涨:
- 检查是否有内存泄漏,使用Instruments工具检测。

检测发现确实有内存泄漏,解决后发现问题还是存在。
-
在发送图片消息前,对图片进行压缩(包括压缩质量和尺寸),然后再发送压缩后的图片。
-
避免直接操作大图,可以使用后台线程进行处理,防止阻塞主线程。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 1. 读取图片(避免使用imageNamed:)
UIImage *originalImage = [UIImage imageWithContentsOfFile:filePath];// 2. 尺寸压缩(限制最大边长为1024) CGFloat maxSize = 1024.0; CGSize scaledSize = [self scaledSizeForImage:originalImage maxLength:maxSize]; // 3. 质量压缩(70%质量) UIImage *compressedImage = [self resizeImage:originalImage toSize:scaledSize]; NSData *imageData = UIImageJPEGRepresentation(compressedImage, 0.7); // 4. 发送压缩后的数据(非原始图片) [self sendImageData:imageData];
});
// 计算缩放尺寸
- (CGSize)scaledSizeForImage:(UIImage *)image maxLength:(CGFloat)maxLength {
CGFloat ratio = MIN(maxLength / image.size.width, maxLength / image.size.height);
return CGSizeMake(image.size.width * ratio, image.size.height * ratio);
}
// 图片重绘
- (UIImage *)resizeImage:(UIImage *)image toSize:(CGSize)targetSize {
UIGraphicsBeginImageContextWithOptions(targetSize, NO, UIScreen.mainScreen.scale);
[image drawInRect:CGRectMake(0, 0, targetSize.width, targetSize.height)];
UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resizedImage;
}
- (CGSize)scaledSizeForImage:(UIImage *)image maxLength:(CGFloat)maxLength {
-
压缩后的图片数据要及时释放不必要的资源,避免在内存中同时存在多张图片,比如在图片展示后,如果原图不再需要,可以将其置为nil,帮助内存回收。(@autoreleasepool)。
处理到此内存暴涨解决了,但是随着发送图片内存还是在持续增加,现在每发送一张图片内存还是要涨10M。(message.compressRatio = 1.0 // 设置压缩率),message.compressRatio = 0.2,每发送一张图片内存也要涨5M。
新问题:
即时通讯App在发送图片消息时每次展示一张图片内存涨10M多。
可能原因分析:
1.图片加载方法不对

[UIImage imageNamed:]的内存缓存特性:
-
系统级缓存无法自动释放
-
特别不适合大图和列表展示场景
-
会自动缓存图片到系统缓存
-
适合重复使用的小图标
-
不适合大图或单次使用的图片
2.内存增长原因:
为了支持 GIF/WebP 等动图格式,showImageView为SDAnimatedImageView,它解码后的帧缓存会增加内存占用。
-
大图被缓存且无法及时释放
-
图片解码后的位图数据占用内存
3.Cell 复用机制
快速滑动时可能同时加载多张大图,旧图片未及时释放
优化方案:
通过以下优化措施,图片展示内存问题应该能得到显著改善。核心要点是:
-
避免使用 imageNamed: 加载大图
-
合理配置 SDAnimatedImageView
-
完善 cell 复用机制
-
使用图片下采样技术
-
滑动时优化资源使用
1.使用正确的图片加载方式,用 [UIImage imageWithContentsOfFile:filePath]替代[UIImage imageNamed:filePath]
优点:
-
不会缓存图片
-
适合大图和单次使用的图片
后台线程解码 + 尺寸适配
// 后台线程处理图片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@autoreleasepool {
// 1. 从文件加载
UIImage *originalImage = [UIImage imageWithContentsOfFile:filePath];
// 2. 压缩图片尺寸 (按需)
CGSize targetSize = CGSizeMake(800, 800); // 根据需求调整
UIGraphicsBeginImageContextWithOptions(targetSize, NO, [UIScreen mainScreen].scale);
[originalImage drawInRect:CGRectMake(0, 0, targetSize.width, targetSize.height)];
UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 3. 主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.showImageView.image = scaledImage;
});
}
});
2.使用 ImageIO 框架高效加载
#import <ImageIO/ImageIO.h>
NSURL *imageURL = [NSURL fileURLWithPath:filePath];
NSDictionary *options = @{(id)kCGImageSourceShouldCache: @NO}; // 禁用解码缓存
CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)imageURL, NULL);
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)options);
UIImage *image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CFRelease(source);
self.showImageView.image = image;
3.使用第三方图片加载库
// 使用SDWebImage示例
#import <SDWebImage/SDWebImage.h>
[self.showImageView sd_setImageWithURL:[NSURL fileURLWithPath:filePath]placeholderImage:nil
completed:^(UIImage *image, NSError *error,
SDImageCacheType cacheType,
NSURL *imageURL) {
// 加载完成回调
}];
4.优化 SDAnimatedImageView 配置
- (SDAnimatedImageView *)showImageView {
if (!_showImageView) {
_showImageView = [[SDAnimatedImageView alloc] init];
_showImageView.contentMode = UIViewContentModeScaleAspectFill;
_showImageView.userInteractionEnabled = YES;
// 添加以下优化配置
_showImageView.shouldIncrementalLoad = YES; // 渐进式加载
_showImageView.maxBufferSize = 1024 * 1024; // 设置合理的缓冲区大小
_showImageView.runLoopMode = NSDefaultRunLoopMode; // 滑动时暂停动画
}
return _showImageView;
}
5.Cell 复用时的内存管理
// 在 cell 的 prepareForReuse 中清理
- (void)prepareForReuse {
[super prepareForReuse];
// 停止动画并释放资源
[self.showImageView stopAnimating];
self.showImageView.currentFrame = nil;
self.showImageView.animationImages = nil;
// 取消未完成的图片加载
[self.showImageView sd_cancelCurrentImageLoad];
}
6.图片尺寸优化(针对大图)
// 使用 ImageIO 进行下采样
- (UIImage *)downsampleImageAtPath:(NSString *)path toSize:(CGSize)size {
NSURL *url = [NSURL fileURLWithPath:path];
NSDictionary *options = @{
(id)kCGImageSourceShouldCache: @NO,
(id)kCGImageSourceShouldAllowFloat: @YES
};
CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
CGFloat maxDimension = MAX(size.width, size.height) * [UIScreen mainScreen].scale;
NSDictionary *downsampleOptions = @{
(id)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
(id)kCGImageSourceShouldCacheImmediately: @YES,
(id)kCGImageSourceThumbnailMaxPixelSize: @(maxDimension)
};
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(source, 0, (CFDictionaryRef)downsampleOptions);
UIImage *image = [UIImage imageWithCGImage:imageRef];
if (imageRef) CFRelease(imageRef);
if (source) CFRelease(source);
return image;
}
7.滑动性能优化
// 在 scrollView 代理中实现以下方法
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// 暂停屏幕外 cell 的动画
for (UITableViewCell *cell in self.tableView.visibleCells) {
if ([cell isKindOfClass:[YourCellClass class]]) {
YourCellClass *yourCell = (YourCellClass *)cell;
[yourCell.showImageView startAnimating];
}
}
// 暂停非可见 cell 的动画
NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows];
for (NSIndexPath *indexPath in self.loadedIndexPaths) {
if (![visiblePaths containsObject:indexPath]) {
YourCellClass *cell = (YourCellClass *)[self.tableView cellForRowAtIndexPath:indexPath];
[cell.showImageView stopAnimating];
}
}
}
其他优化建议:
1.内存警告处理
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// 清除所有图片缓存
[[SDImageCache sharedImageCache] clearMemory];
}
2.使用合适的 SDWebImage 选项:
[self.showImageView sd_setImageWithURL:imageURL
placeholderImage:nil
options:SDWebImageAvoidDecodeImage |
SDWebImageScaleDownLargeImages |
SDWebImageProgressiveLoad
completed:nil];
3.监控内存使用:
- (void)monitorMemoryUsage {
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kerr = task_info(mach_task_self(),
TASK_BASIC_INFO,
(task_info_t)&info,
&size);
if (kerr == KERN_SUCCESS) {
NSLog(@"Memory in use (in MB): %f", info.resident_size / 1024.0 / 1024.0);
}
}
4.配置 SDWebImage 全局参数(AppDelegate 中)
// 设置全局缓存策略
SDImageCacheConfig *cacheConfig = [SDImageCacheConfig defaultCacheConfig];
cacheConfig.maxMemoryCost = 100 * 1024 * 1024; // 100MB 内存缓存
cacheConfig.maxMemoryCount = 50; // 最大缓存图片数量
cacheConfig.shouldDecompressImages = NO; // 禁止自动解压
[SDImageCache sharedImageCache].config = cacheConfig;
