問(wèn)題:
即時(shí)通訊App在發(fā)送圖片消息時(shí)內(nèi)存暴漲導(dǎo)致網(wǎng)絡(luò)請(qǐng)求初始化失?。▋?nèi)存不足OOM),發(fā)送消息失敗。
可能原因分析:
- 有內(nèi)存泄漏。
- 發(fā)送的圖片消息,可能包含大圖,沒(méi)有進(jìn)行壓縮處理,導(dǎo)致內(nèi)存占用過(guò)高。
- 在發(fā)送過(guò)程中,可能同時(shí)進(jìn)行了圖片的讀取和處理,如果圖片很大,處理過(guò)程中會(huì)產(chǎn)生很大的內(nèi)存峰值。
解決方案:
- 對(duì)于發(fā)送消息時(shí)的內(nèi)存暴漲:
- 檢查是否有內(nèi)存泄漏,使用Instruments工具檢測(cè)。

檢測(cè)發(fā)現(xiàn)確實(shí)有內(nèi)存泄漏,解決后發(fā)現(xiàn)問(wèn)題還是存在。
在發(fā)送圖片消息前,對(duì)圖片進(jìn)行壓縮(包括壓縮質(zhì)量和尺寸),然后再發(fā)送壓縮后的圖片。
避免直接操作大圖,可以使用后臺(tái)線程進(jìn)行處理,防止阻塞主線程。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 1. 讀取圖片(避免使用imageNamed:)
UIImage *originalImage = [UIImage imageWithContentsOfFile:filePath];
// 2. 尺寸壓縮(限制最大邊長(zhǎng)為1024)
CGFloat maxSize = 1024.0;
CGSize scaledSize = [self scaledSizeForImage:originalImage maxLength:maxSize];
// 3. 質(zhì)量壓縮(70%質(zhì)量)
UIImage *compressedImage = [self resizeImage:originalImage toSize:scaledSize];
NSData *imageData = UIImageJPEGRepresentation(compressedImage, 0.7);
// 4. 發(fā)送壓縮后的數(shù)據(jù)(非原始圖片)
[self sendImageData:imageData];
});
// 計(jì)算縮放尺寸
- (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;
}
- 壓縮后的圖片數(shù)據(jù)要及時(shí)釋放不必要的資源,避免在內(nèi)存中同時(shí)存在多張圖片,比如在圖片展示后,如果原圖不再需要,可以將其置為nil,幫助內(nèi)存回收。(@autoreleasepool)。
處理到此內(nèi)存暴漲解決了,但是隨著發(fā)送圖片內(nèi)存還是在持續(xù)增加,現(xiàn)在每發(fā)送一張圖片內(nèi)存還是要漲10M。(message.compressRatio = 1.0 // 設(shè)置壓縮率),message.compressRatio = 0.2,每發(fā)送一張圖片內(nèi)存也要漲5M。
新問(wèn)題:
即時(shí)通訊App在發(fā)送圖片消息時(shí)每次展示一張圖片內(nèi)存漲10M多。
可能原因分析:
1.圖片加載方法不對(duì)

[UIImage imageNamed:]的內(nèi)存緩存特性:
系統(tǒng)級(jí)緩存無(wú)法自動(dòng)釋放
特別不適合大圖和列表展示場(chǎng)景
會(huì)自動(dòng)緩存圖片到系統(tǒng)緩存
適合重復(fù)使用的小圖標(biāo)
不適合大圖或單次使用的圖片
2.內(nèi)存增長(zhǎng)原因:
為了支持 GIF/WebP 等動(dòng)圖格式,showImageView為SDAnimatedImageView,它解碼后的幀緩存會(huì)增加內(nèi)存占用。
大圖被緩存且無(wú)法及時(shí)釋放
圖片解碼后的位圖數(shù)據(jù)占用內(nèi)存
3.Cell 復(fù)用機(jī)制
快速滑動(dòng)時(shí)可能同時(shí)加載多張大圖,舊圖片未及時(shí)釋放
優(yōu)化方案:
通過(guò)以下優(yōu)化措施,圖片展示內(nèi)存問(wèn)題應(yīng)該能得到顯著改善。核心要點(diǎn)是:
避免使用 imageNamed: 加載大圖
合理配置 SDAnimatedImageView
完善 cell 復(fù)用機(jī)制
使用圖片下采樣技術(shù)
滑動(dòng)時(shí)優(yōu)化資源使用
1.使用正確的圖片加載方式,用 [UIImage imageWithContentsOfFile:filePath]替代[UIImage imageNamed:filePath]
優(yōu)點(diǎn):
不會(huì)緩存圖片
適合大圖和單次使用的圖片
后臺(tái)線程解碼 + 尺寸適配
// 后臺(tái)線程處理圖片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@autoreleasepool {
// 1. 從文件加載
UIImage *originalImage = [UIImage imageWithContentsOfFile:filePath];
// 2. 壓縮圖片尺寸 (按需)
CGSize targetSize = CGSizeMake(800, 800); // 根據(jù)需求調(diào)整
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.使用第三方圖片加載庫(kù)
// 使用SDWebImage示例
#import <SDWebImage/SDWebImage.h>
[self.showImageView sd_setImageWithURL:[NSURL fileURLWithPath:filePath]placeholderImage:nil
completed:^(UIImage *image, NSError *error,
SDImageCacheType cacheType,
NSURL *imageURL) {
// 加載完成回調(diào)
}];
4.優(yōu)化 SDAnimatedImageView 配置
- (SDAnimatedImageView *)showImageView {
if (!_showImageView) {
_showImageView = [[SDAnimatedImageView alloc] init];
_showImageView.contentMode = UIViewContentModeScaleAspectFill;
_showImageView.userInteractionEnabled = YES;
// 添加以下優(yōu)化配置
_showImageView.shouldIncrementalLoad = YES; // 漸進(jìn)式加載
_showImageView.maxBufferSize = 1024 * 1024; // 設(shè)置合理的緩沖區(qū)大小
_showImageView.runLoopMode = NSDefaultRunLoopMode; // 滑動(dòng)時(shí)暫停動(dòng)畫
}
return _showImageView;
}
5.Cell 復(fù)用時(shí)的內(nèi)存管理
// 在 cell 的 prepareForReuse 中清理
- (void)prepareForReuse {
[super prepareForReuse];
// 停止動(dòng)畫并釋放資源
[self.showImageView stopAnimating];
self.showImageView.currentFrame = nil;
self.showImageView.animationImages = nil;
// 取消未完成的圖片加載
[self.showImageView sd_cancelCurrentImageLoad];
}
6.圖片尺寸優(yōu)化(針對(duì)大圖)
// 使用 ImageIO 進(jìn)行下采樣
- (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.滑動(dòng)性能優(yōu)化
// 在 scrollView 代理中實(shí)現(xiàn)以下方法
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// 暫停屏幕外 cell 的動(dòng)畫
for (UITableViewCell *cell in self.tableView.visibleCells) {
if ([cell isKindOfClass:[YourCellClass class]]) {
YourCellClass *yourCell = (YourCellClass *)cell;
[yourCell.showImageView startAnimating];
}
}
// 暫停非可見(jiàn) cell 的動(dòng)畫
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];
}
}
}
其他優(yōu)化建議:
1.內(nèi)存警告處理
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// 清除所有圖片緩存
[[SDImageCache sharedImageCache] clearMemory];
}
2.使用合適的 SDWebImage 選項(xiàng):
[self.showImageView sd_setImageWithURL:imageURL
placeholderImage:nil
options:SDWebImageAvoidDecodeImage |
SDWebImageScaleDownLargeImages |
SDWebImageProgressiveLoad
completed:nil];
3.監(jiān)控內(nèi)存使用:
- (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 全局參數(shù)(AppDelegate 中)
// 設(shè)置全局緩存策略
SDImageCacheConfig *cacheConfig = [SDImageCacheConfig defaultCacheConfig];
cacheConfig.maxMemoryCost = 100 * 1024 * 1024; // 100MB 內(nèi)存緩存
cacheConfig.maxMemoryCount = 50; // 最大緩存圖片數(shù)量
cacheConfig.shouldDecompressImages = NO; // 禁止自動(dòng)解壓
[SDImageCache sharedImageCache].config = cacheConfig;
