文档章节

大话SDWebImage(三)-- 图片下载层

aron1992
 aron1992
发布于 2019/12/14 17:24
字数 1656
阅读 6
收藏 0

四、图片下载层

SDWebImageDownloader是处理图片下载的类

4.1 图片下载步骤

首先介绍下dispatch_barrierGCD中的dispatch_barrier目的是在并发队列实现串行的效果,创建下载任务SDWebImageDownloaderOperation是以同步任务(dispatch_barrier_sync)+并发队列(barrierQueue)的方式创建的,因为需要实时返回,所以需要使用同步任务的方式。这里为什么是使用dispatch_barrier_sync,因为cancel方法取消操作是以异步任务(dispatch_barrier_async)+并发队列(barrierQueue)的方式取消下载任务,dispatch_barrier_syncdispatch_barrier_async是配对使用的,目的是为了确保取消下载Operation的步骤是在创建下载Operation之后的。
GCD技术不了解的同学可以参考 GCD(一) 队列、任务、串行、并发GCD(二) dispatch_barrier 这两篇文章的介绍

SDWebImageDownloader中的处理流程如下

  • 以同步任务(dispatch_barrier_sync)+并发队列(barrierQueue)的方式创建下载任务SDWebImageDownloaderOperation
  • 添加下载任务SDWebImageDownloaderOperationURLOperations数组中,注册SDWebImageDownloaderOperation的完成回调,在回调中吧任务从URLOperations中删除。URLOperations数组的任务是确保同一个URL同时只有一个下载任务SDWebImageDownloaderOperation进行下载
  • 保存到回调Block(进度回调和结束回调)到下载任务SDWebImageDownloaderOperation的回调数组中
  • 返回token,token中包含了下载URL和回调Block,SDWebImageSDWebImageManager图片管理单例对象取消下载会使用到这个token,目前只有使用到token中的url

真实发起请求以及处理数据地方是在下载任务SDWebImageDownloaderOperation中,使用NSURLSession发起请求,使用NSURLSessionTask处理数据。

SDWebImageDownloaderOperation下载的具体流程如下图所示:

下载的具体流程

4.2 取消处理

图片管理层有个SDWebImageCombinedOperation对象,它持有图片缓存的NSOperation以及他本身cancel操作的回调cancelBlockcancelBlock中持有subOperationToken,也就是下载管理器返回的token,使用这个token,调用下载管理器SDWebImageDownloadercancel方法取消下载的Operation

取消下载通信图如下所示:

image-20191214114357705

这里涉及到许多Block,所以需要特别注意有可能导致的循环引用问题,比如某个对象的block调用中使用了自身,需要使用__weak防止循环引用,下面是SDWebImageManager中的loadImageWithURL:options:progress:completed:方法处理循环引用的方式

    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

4.3 后台下载任务

SDWebImage中提供了一个选项用于支持后台下载SDWebImageDownloaderContinueInBackground,在自带的Demo中可以修改代码如下,来查看代码的调用

cell.customTextLabel.text = [NSString stringWithFormat:@"Image #%ld", (long)indexPath.row];
SDWebImageOptions options = indexPath.row == 0 ? SDWebImageRefreshCached : 0;
// 添加后台下载选项
options |= SDWebImageContinueInBackground;
[cell.customImageView sd_setImageWithURL:[NSURL URLWithString:_objects[indexPath.row]]
                        placeholderImage:placeholderImage
                                 options:options];

开启后台任务,是在调用[dataTask resume]之前,官方文档要求是越早越好,否则可能会出现异常

Call this method as early as possible before starting your task, and preferably before your app actually enters the background.

下面是开启后台下载任务的关键代码:

// 执行后台任务,在下载任务开始的时候马上调用
// beginBackgroundTaskWithExpirationHandler回调中调用`endBackgroundTask`结束后台任务,并且设置`backgroundTaskId`值为`UIBackgroundTaskInvalid`
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
    __weak __typeof__ (self) wself = self;
    UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
    self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
        __strong __typeof (wself) sself = wself;

        if (sself) {
            [sself cancel];

            [app endBackgroundTask:sself.backgroundTaskId];
            sself.backgroundTaskId = UIBackgroundTaskInvalid;
        }
    }];
}

// 正常下载流程 ......

// 如果已经在前台执行了下载任务,那么调用`endBackgroundTask`结束后台任务,并且设置`backgroundTaskId`值为`UIBackgroundTaskInvalid`
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
    return;
}
if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
    UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
    [app endBackgroundTask:self.backgroundTaskId];
    self.backgroundTaskId = UIBackgroundTaskInvalid;
}

4.4 下载选项

  1. 优先级
    • SDWebImageDownloaderLowPriority 低优先级
    • SDWebImageDownloaderHighPriority 高优先级

这两个选项是用于设置SDWebImageDownloaderOperation的优先级的

if (options & SDWebImageDownloaderHighPriority) {
    operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
    operation.queuePriority = NSOperationQueuePriorityLow;
}

通过SDWebImagemanager调用对应的是SDWebImageLowPrioritySDWebImageHighPriority这两个值。

  1. 渐进式加载SDWebImageDownloaderProgressiveDownload

如果设置了该选项,在- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data回调方法中就会处理图像数据,通过progressBlock返回图像,会有性能损耗,默认是关闭的。通过SDWebImagemanager调用对应的是SDWebImageProgressiveDownload值。

  1. SDWebImageDownloaderUseNSURLCache 使用URL缓存选项

如果设置看该选项,创建NSMutableURLRequest的时候cachePllicy会使用NSURLRequestUseProtocolCachePolicy,也就是会使用URL的缓存策略,否则使用NSURLRequestReloadIgnoringLocalCacheData忽略缓存。通过SDWebImagemanager调用对应的是SDWebImageRefreshCached这个值。

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
  1. SDWebImageDownloaderContinueInBackground 后台下载选项

参考之前提到的后台下载任务,默认是不开启的 ,通过SDWebImagemanager调用对应的是SDWebImageLowPrioritySDWebImageContinueInBackground这两个值。

  1. SDWebImageDownloaderScaleDownLargeImages压缩大图选项

这个选项会对解码之后操作60M的图像进行压缩,压缩处理到60M,每次从源图像中取出大小为20M大小的部分,调用CGContextDrawImage绘制到目标图像中,循环直到没有数据位置,下面是图像压缩数据处理的数据图,一张大小为90M的图像,每次取20M大小的图像,写入到目标图像文件中,每次写入到目标图像的大小大概为13.2M,最后一次取出的源文件大小为10M,写入到目标文件大小为0.66M,写入结束之后调用CGBitmapContextCreateImage取出目标图像,完成压缩。

image-20191214151811101

具体的实现代码如下,原来的代码比较长,删除大部分剩余关键代码如下

+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image {
    CGContextRef destContext;
    
		// 使用autoreleasepool防止高内存峰值
    @autoreleasepool {
        CGImageRef sourceImageRef = image.CGImage;
        CGImageRef sourceTileImageRef;
        // 计算循环获取的次数
        int iterations = (int)( sourceResolution.height / sourceTile.size.height );

        for( int y = 0; y < iterations; ++y ) {
            @autoreleasepool {
               // 获取原图像中的部分,计算对应目标图像中的Frame
                sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
                destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
                sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
                // 最后一次的高度做特殊处理
                if( y == iterations - 1 && remainder ) {
                    float dify = destTile.size.height;
                    destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
                    dify -= destTile.size.height;
                    destTile.origin.y += dify;
                }
                // 绘制部分图像
                CGContextDrawImage( destContext, destTile, sourceTileImageRef );
                CGImageRelease( sourceTileImageRef );
            }
        }
        // 获取最终图像
        CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
       
        return destImage;
    }
}

4.5 预下载

SDWebImagePrefetcher提供了预下载的功能,最终也是通过SDWebImageManager处理下载,这样统一了底层的下载逻辑,如果同时存在预下载操作和正常下载操作,底层只会有一个下载操作,可以有多个回调回调数据。预下载是串行处理的,可以通过以下两个方法进行预下载。

  • - (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls

  • - (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock

© 著作权归作者所有

aron1992

aron1992

粉丝 66
博文 112
码字总数 183269
作品 0
厦门
程序员
私信 提问
sdwebimage图片加载不出来,报错 -1100,"Downloaded image has 0 pixels"

找了五六个小时原因,后来发现是图片格式不对,后台给的二进制文件是webp格式的,但是图片的后缀确是.png(这里给上传图片到服务的小伙伴点一千万个赞,感谢这个“可爱”的同事帮忙把图片后缀...

业界小白
2018/11/13
1.5K
0
使用SDWebImage时常遇到的问题

1. 图片文件缓存的时间有多长:1周 2. SDWebImage 的内存缓存是用什么实现的? 3. SDWebImage 的最大并发数是多少? 4. SDWebImage 支持动图吗?GIF 5. SDWebImage的最大超时时长 6. SDWebIm...

_小迷糊
2018/05/07
0
0
使用SDWebImage下载高分辨率图,导致内存暴增的解决办法

最近,遇到一个问题,有个控制器,一进去就crash,而且手机非常的烫,用instrument跑了跑,发现内存暴增几百兆;如图:   图中可以看出,内存暴增的罪魁祸首是YYImage,再进一步定位问题,如...

ocarol
2016/07/25
0
0
避免 iOS 组件依赖冲突的小技巧

问题缘由 本文以 YBImageBrowser 组件举例。 YBImageBrowser 依赖了 SDWebImage,在使用 CocoaPods 集成到项目中时,可能会出现一些依赖冲突的问题,最近社区提了多个 Issues 并且在 Insigh...

波儿菜
2019/08/29
0
0
iOS 图片加载框架SDWebImage详解

目的 在使用SDWebImage加载图片时,尤其是加载gif等大图时,SDWebImage会将图片缓存在内存中,这样是非常吃内存的,这时我们就需要在适当的时候去释放一下SDWebImage的内存缓存,才不至于造成...

FBY展菲
2017/10/31
412
0

没有更多内容

加载失败,请刷新页面

加载更多

每天AC系列(六):有效的括号

1 题目 LeetCode第20题,这题比较简单,匹配括号. 2 栈 这是栈的典型应用,括号匹配,当然不需要直接使用栈,使用一个StringBuilder即可: if(s.isEmpty()) return true;char a = s.charAt(0);...

Blueeeeeee
今天
27
0
Spring AOP-06-切入点类型

切入点是匹配连接点的拦截规则。之前使用的是注解@Pointcut,该注解是AspectJ中的。除了这个注解之外,Spring也提供了其他一些切入点类型: • 静态方法切入点StaticMethodMatcherPointcut •...

moon888
昨天
90
0
Class Loaders in Java

1. Introduction to Class Loaders Class loaders are responsible for loading Java classes during runtime dynamically to the JVM (Java Virtual Machine). Also, they are part of the ......

Ciet
昨天
96
0
以Lazada为例,看电商系统架构演进

什么是Lazada? Lazada 2012年成立于新加坡,是东南亚第一电商,2016年阿里投资10亿美金,2017年完成对lazada的收购。 业务模式上Lazada更偏重自营,类似于亚马逊,自建仓储和为商家提供服务...

春哥大魔王的博客
昨天
62
0
【自用】 Flutter Timer 简单用法

dart: void _startTime() async { _timer = Timer(Duration(seconds: sec), () { fun(xxx,yyy,zzz); }); } @override void dispose() { _timer.cancel()......

Tensor丨思悟
昨天
65
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部