OC学习43:iOS多个UIImageView加载高清大图

张建 lol

前言

在项目中遇到了加载高清大图的场景,处理不好占用大量的内存,造成性能问题,影响用户体验。

如何去解决加载高清大图这个难点呢?先看看iOS中图片的加载流程

iOS中图片的加载流程

  1. 加载图片数据(尚未解码)

  2. 解码图片数据

  3. 缓存图片数据

  4. 渲染图片数据

性能问题一般是发生在 解码图片 这个步骤上,SDWebImage 解码图片解码的代码是在 SDWebImageCoderHelper 中的 decodedImageWithImage 函数中,并 缓存起来,以保证 tableViews/collectionViews 交互更加流畅,如果是 加载高分辨率图片 的话,会适得其反,有可能造成 内存占用过高内存峰值过高 的问题

优化的思路

  • SDWebImage 加载高清图的性能问题,主要是其直接加载高清图原来尺寸导致的,而 加载一张图片的内存占用 = 图片Width * 图片Height * 4

  • 对于高分辨率的图片,应该在 图片解压 后,禁止缓存解码后的数据

1
2
[[SDImageCache sharedImageCache] setShouldDecompressImages:NO];
[[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];

当然,你也可以设置 SDWebImage 的其他参数,比如是 否缓存到内存以及内存缓存最高限制 等,来保证内存安全:

代码实现

  1. APPDelegate中设置相关参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)imageLoadingSettings {
// 最大缓存周期 一周
    [SDImageCache sharedImageCache].config.maxCacheAge = 3600 * 24 * 7;
// 最大内存限制 20M
    [SDImageCache sharedImageCache].maxMemoryCost = 1024 * 1024 * 20;
// 不缓存到内存
    [SDImageCache sharedImageCache].config.shouldCacheImagesInMemory = NO;
// 不解压图片
    [SDImageCache sharedImageCache].config.shouldDecompressImages = NO;
// 不解压图片
    [SDWebImageDownloader sharedDownloader].shouldDecompressImages = NO;
// iOS就不会把整个文件全部读取的内存了,而是将文件映射到进程的 地址空间 中,这么做并不会占用实际内存。这样就可以解决内存占用过高的问题。
    [SDImageCache sharedImageCache].config.diskCacheReadingOptions = NSDataReadingMappedIfSafe;

}
  1. ViewController中实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Decompress 解码
static BOOL SDImageCacheOldShouldDecompressImages = YES;
static BOOL SDImagedownloderOldShouldDecompressImages = YES;
- (void)viewDidLoad {
[super viewDidLoad];

// 设置图片缓存方式 防止图片过大崩溃的情况
SDImageCache *canche = [SDImageCache sharedImageCache];
SDImageCacheOldShouldDecompressImages = canche.config.shouldDecompressImages;
canche.config.shouldDecompressImages = NO;
SDWebImageDownloader *downloder = [SDWebImageDownloader sharedDownloader];
SDImagedownloderOldShouldDecompressImages = downloder.shouldDecompressImages;
downloder.shouldDecompressImages = NO;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.
[[SDWebImageManager sharedManager].imageCache clearMemory];
[[SDWebImageManager sharedManager].imageCache clearDiskOnCompletion:nil];
}
- (void)dealloc {
[[SDImageCache sharedImageCache] clearMemory];
SDImageCache *canche = [SDImageCache sharedImageCache];
canche.config.shouldDecompressImages = SDImageCacheOldShouldDecompressImages;
SDWebImageDownloader *downloder = [SDWebImageDownloader sharedDownloader];
downloder.shouldDecompressImages = SDImagedownloderOldShouldDecompressImages;
}

加载大图的解决方案

  1. 显示大图时,传统加载方法会造成内存暴涨,造成主线程阻塞,甚至造成程序crash。可以使用 CATiledLayer 显示,CATiledLayer 类似瓦片视图,他将需要绘制的内容分割成许多小块,然后异步绘制相应的小块,这样就节约了处理时间和内存。
  • 它不需要你自己计算分块显示的区域,它自己直接提供,你只需要根据这个区域计算图片相应区域,然后画图就可以了。

  • 它是在其他线程画图,不会因为阻塞主线程而导致卡顿。

  • 它自己实现了只在屏幕区域显示图片,屏幕区域外不会显示,而且当移动图片时,它会自动绘制之前未绘制的区域,当你缩放时它也会自动重绘。

  1. CATiledLayer 是为载入大图造成的性能问题提供的一个解决方案,具体如何划分小块和缩放时的加载策略,与CATiledLayer三个重要属性有关:
  • levelsOfDetail:levelsOfDetail 指的是该图层缓存的缩小 LOD 数目,默认值为1,每进一级会对前一级分辨率的一半进行缓存,图层的 levelsOfDetail 最大值,对应至少一个像素点。

  • levelsOfDetailBias:levelsOfDetailBias 指的是该图层缓存的放大LODB数目,它是layer的放大级别重绘设置,默认为0,即不会额外缓存放大层次,每进一级会对前一级两倍分辨率进行缓存。

  • tileSIze:(默认是256x256)tiledSize是layer划分视图区域最大尺寸,主要是影响layer的切片数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@interface ViewController ()<CALayerDelegate>
// 构建一个容器来展示大图,保证其contenSize与图片尺寸大小一致
@property (nonatomic, weak) IBOutlet UIScrollView *scrollView;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

// 创建图层,并设置属性信息
CATiledLayer *tileLayer = [CATiledLayer layer];

CGFloat scale = UIScreen.mainScreen.scale; // 确保scale比例一致
tileLayer.frame = CGRectMake(0, 0, 3972/scale,15718/scale);// 图片像素
tileLayer.delegate = self;
tileLayer.tileSize = CGSizeMake(256/scale, 256/scale); // 每个瓷砖块的大小
[self.scrollView.layer addSublayer:tileLayer];

self.scrollView.contentSize = tileLayer.frame.size;

// 刷新当前屏幕Rect
[tileLayer setNeedsDisplay];
}

// 当滑动到不同区域时会调用此方法
- (void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx
{
// 确定坐标信息
CGRect bounds = CGContextGetClipBoundingBox(ctx);
NSInteger x = floor(bounds.origin.x / layer.tileSize.width);
NSInteger y = floor(bounds.origin.y / layer.tileSize.height);

// 加载小图
NSString *imageName = [NSString stringWithFormat: @"zz_%02i_%02i", x, y];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"jpg"];
UIImage *tileImage = [UIImage imageWithContentsOfFile:imagePath];

// 在TiledLayer上绘制图片
UIGraphicsPushContext(ctx);
[tileImage drawInRect:bounds];
UIGraphicsPopContext();
}
  • Post title:OC学习43:iOS多个UIImageView加载高清大图
  • Post author:张建
  • Create time:2023-05-06 16:33:15
  • Post link:https://redefine.ohevan.com/2023/05/06/OC/OC学习43:iOS多个UIImageView加载高清大图/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.
On this page
OC学习43:iOS多个UIImageView加载高清大图