概述
SDWebImage是一个强大的图片下载框架,利用异步加载和内存+磁盘两级缓存处理,高效优雅的解决了图片下载的问题.
This library provides an async image downloader with cache support. For convenience, we added categories for UI elements like UIImageView, UIButton, MKAnnotationView.
引用官方文档的一句话:异步下载,支持缓存,并且利用分类的方式为UIImageView,UIButton,MKAnnotationView等控件添加了方便的下载方法,简言之就是,强大且好用.
一个常见的下载方法的调用是这样的:
[self.imageview sd_setImageWithURL:[NSURL URLWithString:@"http://www.pp3.cn/uploads/201609/2016092105.jpg"] placeholderImage:[UIImage imageNamed:@"dragon.jpg"] options:SDWebImageCacheMemoryOnly progress:^(NSInteger receivedSize, NSInteger expectedSize) {
NSLog(@"%ld",expectedSize);
NSLog(@"%ld",receivedSize);
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(@"Finished!");
}];
核心功能类:
UIImageView+WebCache**,UIButton+WebCache,MKAnnotationView+WebCache
作为直接面对用户调用的接口,框架通过给控件添加分类的形式方便的赋予了下载图片的功能.而这一切得益于后台几个类的相互配合;
SDWebImageManager
持有SDWebCache和SDWebImageDownloader对象,从而决定一个图片是从已有的缓存中取到,还是需要从网络下载;
SDWebImageDownloader
维护一个操作队列,并且管理下载过程中的进度和下载好的回调;
SDImageCache
负责为缓存开辟空间包括内存缓存和磁盘缓存,写入缓存,管理缓存,读出缓存,清理缓存等工作;
SDWebImageDownloaderOperation
这是一个NSOperation的子类,代表一个下载操作,以回调block或者代理的方式取到下载的图片数据或者错误信息.
按照软件工程的一般套路,我们剖析代码的思路也是:从整体到局部,从上层到下层,按功能线回溯,各行其是,分而治之
UI层
UI层通过给UIImageView,UIButton,MKAnnotationView添加分类,拓展了一个下载图片的方法,框架针对不同的情形,给出了多种方法可供调用,但是本质上都是调用了下面这个方法:
UIView+WebCacheOperation
考虑到很多UIView的子控件都需要持有当前的operation(s),作者添加了一个UIView的分类,用来存储加载视图上面的operation(s),同时添加了一系列方法来维护这些operation(s).
//设置图片下载操作
- (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key;
//取消图片下载操作
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key;
///移除图片操作
- (void)sd_removeImageLoadOperationWithKey:(NSString *)key;
但是为什么要这么做呢?举例来说,tableViewCell上面有很多UIImageView,快速滑动的时候,其实很多imageView快速出来,又快速消失了,这个时候把消失了的视图上面的下载操作取消掉,是更好的选择.
UIImageView+WebCache
这个分类里面,提供了很多便利化的方法可供调用,但是最终都会调到下面这个方法的实现,我在这个方法里面添加了注释:
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
//url: 图片对应的URL
//placeholder: 预显示的占位图片,从项目的Bundle里面加载;
//option: SDWebImageOptions的枚举,定义了一系列用户的定制项;
//progressBlock: SDWebImageDownloaderProgressBlock下载进度的回调block,用户可以在这里取到要下载的文件大小,和已下载的文件大小,用来跟踪下载进度或者定制进度条;
//completedBlock: 下载完成的回调block,包含image,error,url,以及完成与否的bool值.
[self sd_cancelCurrentImageLoad];//取消对应当前控件对应的下载任务
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//将当前UIImageView对象与其URL进行关联: UIImageView <--> Url
if (!(options & SDWebImageDelayPlaceholder)) {//
dispatch_main_async_safe(^{
self.image = placeholder;//添加占位图片
});
}
if (url) {
// check if activityView is enabled or not
// 添加下载指示器
if ([self showActivityIndicatorView]) {
[self addActivityIndicator];
}
//调用SDWebImageManager单例的下载方法,返回一个遵循了SDWebImageOperation协议的operation对象.
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
[wself removeActivityIndicator];//完成移除指示器
if (!wself) return;
dispatch_main_sync_safe(^{
if (!wself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{
completedBlock(image, error, cacheType, url);
return;
}
else if (image) {
wself.image = image;
[wself setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
wself.image = placeholder;
[wself setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];//添加当前控件的operation
} else {//当url为空的时候
dispatch_main_async_safe(^{
[self removeActivityIndicator];//移除指示器
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);//调用完成的block抛出异常信息
}
});
}
}
其他的控件上的下载操作和这个类似,这里就不一一列举了,方法调到这里就会离开UI层而来到控制层.
Tips:
1.OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
这是利用Runtime来给分类添加属性的方法,object是要关联属性的对象,key是一个常量字符,value是要关联的属性,policy是关联策略.
2.
dispatch_main_async_safe(block)
这是自定义的宏,用来保证block只在主线程异步执行.
控制层
控制层利用一个SDWebImageManager来协调图片是从网路下载还是从缓存中获取,并且决定是否刷新缓存,SDWebImageManager持有SDImageCache和SDWebImageDownloader对象.
SDWebImageManager
职能:
- 发出下载指令,并取得下载的操作
- 指定Url转换成缓存用的唯一key
- 发出缓存指令,给定key
- 检查缓存是否存在,包括在缓存和磁盘中
- 取消所有的下载操作
实现:
定义SDWebImageOptions枚举
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
SDWebImageRetryFailed = 1 << 0,//1 是否重试
SDWebImageLowPriority = 1 << 1,//2 当前若有UI操作的时候,延迟下载
SDWebImageCacheMemoryOnly = 1 << 2,//4 只允许内存缓存\不允许磁盘缓存
SDWebImageProgressiveDownload = 1 << 3,//8 采用渐进式加载\默认情况下是图片下载完成之后才渲染
SDWebImageRefreshCached = 1 << 4,//16 刷新缓存
SDWebImageContinueInBackground = 1 << 5,//32 开启后台下载功能
SDWebImageHandleCookies = 1 << 6,//64 为请求保存Cookies
SDWebImageAllowInvalidSSLCertificates = 1 << 7,//128 允许无效的SSL证书
SDWebImageHighPriority = 1 << 8,//256 把下载任务移到任务队列的front位置,保证优先级最高
SDWebImageDelayPlaceholder = 1 << 9,//512 延迟加载占位图片
SDWebImageTransformAnimatedImage = 1 << 10,//1024 使用动画图片
SDWebImageAvoidAutoSetImage = 1 << 11//2048 避免自动设置图片
};
Manager通过SDWebImageDownloader对象下达加载图片的指令,都在下面这个方法里面:
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock
1.判断url是否有效,无效则把url指定为nil,如果是以前就失败了的url并且options为SDWebImageRetryFailed,则调用completedBlock给出错误提示,方法返回当前操作,否则进行步骤2;
2.往runningOperations增加operation,并把url转换成标识图片的key;
3.调用imageCache的- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock方法,从内存和磁盘里查询缓存,如果缓存存在调用completedBlock,返回查找到的图片,否则进行步骤4;
4.如果没有命中缓存,或者指定需要刷新缓存,则开始启动imageDownloader的下载程序- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock,把返回的operation添加到下载队列中,这里并没有提供磨人的progressBlock的实现,只是在completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished)里面处理下载结束的状态,错误信息,返回下载好的图片并把图片在存到缓存中.
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
//completedBlock为空时候的异常处理
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
//对url的纠错处理,如果传入的是字符串类型,则强制转换成NSURL
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
//如果是其他不能强制转换成NSURL的类型,或者是NSNull,则直接置为nil,后面碰到nil就会走处理nil的部分
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
//failedURLs用来保存失败了的urls,加互斥锁用保证多线程安全
BOOL isFailedUrl = NO;
if (url) {
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
}
//如果给定的url不满足条件,开始往失败的分支里面走,其实就是调用completedBlock给定空的image
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
return operation;
}
//如果能走到这里来,说明操作本次操作可以执行,就添加到正在执行的operations
//同样加互斥锁保证线程安全
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
//通过url转换成key,默认情况是直接返回url.absoluteString,如果用户有给定对url的筛选规则,则考虑自定义的规则
NSString *key = [self cacheKeyForURL:url];
//调用SDImageCache的查询缓存的方法,通过key来查找缓存
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
//如果操作中途被取消,则用线程安全的方式取消正在进行中的operation,然后方法返回
if (operation.isCancelled) {
[self safelyRemoveOperationFromRunning:operation];
return;
}
//如果用户要刷新缓存 && 没有自己实现下载图片的方法,那就要走框架给定的下载方法
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
if (cachedImage && options & SDWebImageRefreshCached) {
//如果命中缓存图片,但是用户强制刷新缓存,那就先提供缓存图片,同时再去服务端下载一边,当图片的内容改变了,但是url没有变的时候适用
[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
//如果没有图片或者强制刷新,则开始从服务器加载图片
SDWebImageDownloaderOptions downloaderOptions = 0;
//...
//开启创建下载任务
SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
//如果操作被取消,那就什么都不做
} else if (error) {
//下载出现错误的处理
[self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
//...
}
else {
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
} else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//对动画图片(GIF)的处理
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
//调用SDWebImageCache的存储方法,开始往内存或者磁盘里面缓存图片内容,key是上文中给定的url,在SDWebImageCache类的内部还会对key做进一步的处理
// pass nil if the image was transformed, so we can recalculate the data from the image
[self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
}
//调用completetionBlock返回结果
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
});
} else {
if (downloadedImage && finished) {
//调用SDWebImageCache的存储方法,开始往内存或者磁盘里面缓存图片内容,key是上文中给定的url,在SDWebImageCache类的内部还会对key做进一步的处理
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
//调用completetionBlock返回结果
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
if (finished) {
//安全移除完成了的操作
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
} else if (cachedImage) {
//命中缓存并且不刷新缓存则,直接回调completetionBlock,返回缓存
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
//安全移除完成了的操作
[self safelyRemoveOperationFromRunning:operation];
} else {
//既没有缓存,也不允许下载,则返回错误
// Image not in cache and download disallowed by delegate
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}];
//返回当前操作对象
return operation;
}
Tips:
1.类型纠错
有时候我们误把NSString类型当做NSURL,或者传递的是非URL得类型,框架会帮我们做类型纠错,把字符串类型转换成NSURL,如果是非URL类型,就转换成nil,以免造成后面的误操作.
2.线程加锁
BOOL isFailedUrl = NO;
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];//
}
框架定义了一个NSMutableSet类型的failedURLs,用来存储那些失败了的urls,但是考虑到多个线程要操作这个结构,所以用@synchronized加了个互斥锁,以免造成线程安全问题.
//添加当前下载操作
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];//
}
//移除当前下载操作
@synchronized (self.runningOperations) {
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
//移除所有当前下载操作
@synchronized (self.runningOperations) {
NSArray *copiedOperations = [self.runningOperations copy];
[copiedOperations makeObjectsPerformSelector:@selector(cancel)];
[self.runningOperations removeObjectsInArray:copiedOperations];
}
//判断有没有正在进行的下载操作
- (BOOL)isRunning {
BOOL isRunning = NO;
@synchronized(self.runningOperations) {
isRunning = (self.runningOperations.count > 0);
}
return isRunning;
}
runningOperations是一个可变数组,可以用以对当前的operations进行操作,同样需要保证在多线程环境下的安全性.
SDWebImageDownloader
这个类其实已经很接近下载操作了,但是还是一个中间层.
职能:
1.配置NSURLSession的默认配置(包括请求头信息,超时时间,urlCredential等),生成NSURLSession对象;
2.维护一个下载队列downloadQueue,可以往队列里面添加任务,删除任务,或者调配任务的执行顺序;
3.根据session和request创建operation对象,并加到操作队列里面,指定下载任务和操作的对应关系;
4.把task的返回数据的代理方法,指向对应的operation的代理方法里面.
实现:
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
__strong __typeof (wself) sself = wself;
//设置请求的时间间隔
NSTimeInterval timeoutInterval = sself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
//设置缓存策略,创建urlRequest
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
//设置请求头信息
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
} else {
request.allHTTPHeaderFields = sself.HTTPHeaders;
}
//创建下载的操作对象operation,关联了request和session
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
//是否解压缩图片
operation.shouldDecompressImages = sself.shouldDecompressImages;
//设置请求的身份验证信息
if (sself.urlCredential) {
operation.credential = sself.urlCredential;
} else if (sself.username && sself.password) {
operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
}
//设置队列优先级
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//将操作加入到队列中
[sself.downloadQueue addOperation:operation];
//设置队列中任务的执行顺序,默认是FIFO
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}
//返回operation
return operation;
}];
}
另外在请求接收数据的代理方法里面,会根据task对应的dataOperation请用指定的代理方法,这样就实现了downloader这个类只是起协调作用,真正接受处理数据还是在operation里面,下面会介绍:
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
}
SDWebImageDownloaderOperation
正在处理下载任务和接受数据的地方.
职能:
1.由于继承自NSOperation所以,本类有一系列对operation的操作,例如,start,cancel,done,reset,finished;
2.持有request和session,如果为空则在start的方法里面创建请求和发起dataTask;
3.在代理方法里面接受和处理数据.
实现:
- (void)start {
//取消和重置当前操作
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
//TARGET_OS_IOS || TARGET_OS_TV 如果是iOS平台或者TV平台,则会在应用切到后台的时候开启后台下载任务
#if SD_UIKIT
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;
}
}];
}
#endif
NSURLSession *session = self.unownedSession;
if (!self.unownedSession) {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
//为下载任务创建session,当delegateQueue为nil的时候,会默认在一个串行队列中执行代理方法
self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
session = self.ownedSession;
}
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}
[self.dataTask resume];
if (self.dataTask) {
//处理任务进度的回调
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
});
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
}
#if SD_UIKIT
//结束后台任务
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;
}
#endif
}
start方法是发起下载任务的地方,并且考虑到了后台任务的情况,对于iOS平台和TV平台,当应用切换到后台的时候,会以一个指定的id继续下载,在任务开始的收会处理任务进度的回调和发送一个SDWebImageDownloadStartNotification的通知.
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
//'304 Not Modified' 表明服务端的内容没有做修改,这种情形会单独考虑
if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
self.expectedSize = expected;
//获取到将要接受的数据的二进制形式的长度,调用progressBlock处理下载进度
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, expected, self.request.URL);
}
self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
self.response = response;
//在主线程发出一个SDWebImageDownloadReceiveResponseNotification
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];
});
} else {
NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
//如果响应状态码为304服务端的内容没有做修改,这时候不用去接受图片数据,只需要取消当前操作并返回缓存中的图片就好了
//这样能节省用户流量,提高效率
if (code == 304) {
[self cancelInternal];
} else {
[self.dataTask cancel];
}
//在主线程发出SDWebImageDownloadStopNotification的通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];
[self done];
}
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
由于框架用的直接是系统的UrlSession加载数据,所以在接收到response的时候需要声明接收数据的NSData对象,对于304的隔离处理能有效的帮助用户节省流量和提高图片显示效率.
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
这里主要是将接收到的图片二进制数据拼接到imageData里面去,并处理下载的进度情况,值得注意的是如果用户指定了以边加载边显示的方式渲染图片的话,这里做了很多画图的工作,这里就不详述图片的绘制过程,有兴趣的同学请跳
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
下载完成之后,对图片数据进行尺寸的调整和压缩,并且调用回调block,至此从服务端下载图片的流程就能走通了.
SDImageCache
这里真正执行图片缓存的地方,涉及到缓存的增,删,查找的诸多操作,还有很多异步的文件操作..
耳熟能详的枚举:
typedef NS_ENUM(NSInteger, SDImageCacheType) {
SDImageCacheTypeNone,//不缓存
SDImageCacheTypeDisk,//磁盘缓存
SDImageCacheTypeMemory//内存缓存
};
根据manager给定的key(默认是图片的url)生成存储文件的文件名,这里就是采用MD5摘要算法,生成唯一文件名.
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
const char *str = key.UTF8String;
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];
return filename;
}
在高版本的SD中,作者把判断图片文件类型的方法单独到了一个NSData+ImageContentType的类里面,用到的判断依据是:拿到一张图片十六进制数据的第一个字节进行判断,然后再根据不同的结果返回图片的真实类型。只要是同一种类型的图片,它十六进制数据的第一个字节都是相同的,JPEG图片十六进制数据的第一个字节是0xFF,PNG图片十六进制数据的第一个字节是0x89,GIF图片十六进制数据的第一个字节是0x47.
核心方法:查询缓存
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
//查看内存缓存
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
NSData *diskData = nil;
if ([image isGIF]) {
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
}
if (doneBlock) {
doneBlock(image, diskData, SDImageCacheTypeMemory);
}
return nil;
}
//查看磁盘缓存
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
// 在用之前就判断operation是否被取消了,作者考虑的非常严谨
return;
}
@autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
if (doneBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
});
return operation;
}
结语
致此已经分析了SDWebImage框架的核心内容,可见框架的博大,还没有涉及到的以后再补充,源代码是程序员智慧的结晶,向大神致敬.
网友评论