这是一个非常经典的 iOS 内存管理问题。直接加载 500MB 的大图会导致崩溃,主要是因为:
-
**内存峰值过高**:UIImage 的 `imageNamed:` 或 `imageWithContentsOfFile:` 会将整个图片数据解码到内存中
-
**纹理内存限制**:iOS 对单个纹理有大小限制(通常 4096x4096 左右)
-
**OOM 崩溃**:应用内存使用超过系统限制,被系统强制终止
解决方案
以下是几种有效的解决方案,从简单到复杂排列:
方案 1:使用 ImageIO 框架进行分块加载(推荐)
这是最专业和高效的解决方案,可以只加载当前显示区域需要的图片数据。
```objectivec
#import <ImageIO/ImageIO.h>
@interface LargeImageViewController () <UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, assign) CGSize imageSize;
@property (nonatomic) CGImageSourceRef imageSource;
@end
@implementation LargeImageViewController
- (void)viewDidLoad {
super viewDidLoad\];
\[self setupScrollView\];
\[self loadLargeImage\];
}
- (void)setupScrollView {
self.scrollView = \[\[UIScrollView alloc\] initWithFrame:self.view.bounds\];
self.scrollView.delegate = self;
self.scrollView.minimumZoomScale = 1.0;
self.scrollView.maximumZoomScale = 3.0;
\[self.view addSubview:self.scrollView\];
self.imageView = \[\[UIImageView alloc\] init\];
\[self.scrollView addSubview:self.imageView\];
}
- (void)loadLargeImage {
NSURL \*imageURL = \[NSURL fileURLWithPath:\[\[NSBundle mainBundle\] pathForResource:@"largeImage" ofType:@"jpg"\]\];
// 1. 创建图片源,但不立即加载到内存
self.imageSource = CGImageSourceCreateWithURL((CFURLRef)imageURL, NULL);
// 2. 获取图片属性(不加载像素数据)
NSDictionary \*options = @{(id)kCGImageSourceShouldCache: @NO};
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(self.imageSource, 0, (CFDictionaryRef)options);
// 3. 获取图片尺寸
CGFloat width = \[(NSNumber \*)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth) floatValue\];
CGFloat height = \[(NSNumber \*)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight) floatValue\];
self.imageSize = CGSizeMake(width, height);
CFRelease(imageProperties);
// 4. 设置滚动视图内容大小
self.scrollView.contentSize = self.imageSize;
self.imageView.frame = CGRectMake(0, 0, self.imageSize.width, self.imageSize.height);
// 5. 加载初始可见区域的图片
\[self updateImageViewWithZoomScale:self.scrollView.zoomScale\];
}
// 根据当前显示区域加载对应的图片块
- (void)updateImageViewWithZoomScale:(CGFloat)zoomScale {
// 计算当前可见的矩形区域
CGRect visibleRect = \[self visibleRectForZoomScale:zoomScale\];
// 创建加载选项,只解码可见区域
NSDictionary \*options = @{
(id)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
(id)kCGImageSourceCreateThumbnailWithTransform: @YES,
(id)kCGImageSourceThumbnailMaxPixelSize: @(MAX(visibleRect.size.width, visibleRect.size.height)),
(id)kCGImageSourceShouldCacheImmediately: @YES,
};
// 创建缩略图(实际是可见区域的图片)
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(self.imageSource, 0, (CFDictionaryRef)options);
if (imageRef) {
self.imageView.image = \[UIImage imageWithCGImage:imageRef\];
CGImageRelease(imageRef);
}
}
- (CGRect)visibleRectForZoomScale:(CGFloat)zoomScale {
CGRect visibleRect;
visibleRect.origin = self.scrollView.contentOffset;
visibleRect.size = self.scrollView.bounds.size;
visibleRect.size.width /= zoomScale;
visibleRect.size.height /= zoomScale;
return visibleRect;
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView \*)scrollView {
\[self updateImageViewWithZoomScale:scrollView.zoomScale\];
}
- (void)scrollViewDidZoom:(UIScrollView \*)scrollView {
\[self updateImageViewWithZoomScale:scrollView.zoomScale\];
}
- (UIView \*)viewForZoomingInScrollView:(UIScrollView \*)scrollView {
return self.imageView;
}
- (void)dealloc {
if (_imageSource) {
CFRelease(_imageSource);
}
}
@end
\`\`\`
#### 方案 2:使用 CATiledLayer 进行分片渲染
这种方法更适合超大型图片,系统会自动管理内存,按需加载不同的缩放级别。
\`\`\`objectivec
#import \