iOS今日看点程序员

SDWebImage 解析笔记

2016-12-19  本文已影响583人  林大鹏

项目中一直都有使用SDWebImage,对这个框架有一定的了解,但是体系却未能贯通,因此特地整理下,主要参考:

iOS 源代码分析 --- SDWebImage

SDWebImage源码剖析(-)

SDWebImage源码剖析(二)

蝶.jpg

一. 简介

SDWebImage提供了一个异步下载图片并且支持缓存的UIImageView分类。
主要逻辑为:

主要用到的对象:

1、UIImageView (WebCache)类别,入口封装,实现读取图片完成后的回调

2、SDWebImageManager,对图片进行管理的中转站,记录那些图片正在读取。
向下层读取Cache(调用SDImageCache),或者向网络请求下载对象(调用SDWebImageDownloader) 。
实现SDImageCacheSDWebImageDownloader的回调。

3、SDImageCache,根据URL的MD5生成key对图片进行存储和读取(实现存在内存中或者存在硬盘上两种实现)
实现图片和内存清理工作。

4、SDWebImageDownloader,根据URL向网络读取数据(实现部分读取和全部读取后再通知回调两种方式)

其他类:
SDWebImageDecoder,异步对图像进行了一次解压。

具体流程图:


SDWebImage实现流程图.png

SDWebImage 加载图片的流程 :

  1. 入口 setImageWithURL:placeholderImage:options:会先把placeholderImage显示,然后 SDWebImageManager 根据 URL 开始处理图片。

  2. 进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.

  3. 先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate回调 imageCache:didFindImage:forKey:userInfo:SDWebImageManager

4.SDWebImageManagerDelegate回调 webImageManager:didFinishWithImage:UIImageView+WebCache 等前端展示图片。

  1. 如果内存缓存中没有,生成NSInvocationOperation 添加到队列开始从硬盘异步查找图片是否已经缓存。

  2. 根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:

  3. 如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate回调imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。

  4. 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:

  5. 共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。

  6. 图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。

  7. connection:didReceiveData: 中利用ImageIO 做了按图片下载进度加载效果。

  8. connectionDidFinishLoading:数据下载完成后交给 SDWebImageDecoder做图片解码处理。

  9. 图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。

  10. 在主线程 notifyDelegateOnMainThreadWithInfo:宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo:回调给 SDWebImageDownloader

  11. imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。

  12. 通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。

  13. 将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。

  14. SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。

  15. SDWebImage 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。

  16. SDWebImagePrefetcher 可以预先下载图片,方便后续使用。

二. 架构简介

A.架构图:

SDWebImageView_relationship.jpeg

UIImageView+WebCacehUIButton+WebCache直接为UIkit框架提供接口,而SDWebImageManger负责处理和协调SDWebImageDownloaderSDWebImageCache并与UIkit层进行交互。

三. 具体分析

1.UIImageView+WebCache

A.框架常用入口

// 所有设置图片最终都会调用这个方法
- (void)sd_setImageWithURL:(NSURL *)url 
      placeholderImage:(UIImage *)placeholder {
  [self sd_setImageWithURL:url 
        placeholderImage:placeholder 
                 options:0 
                progress:nil 
               completed:nil];
  }

该接口调用下面这个方法:

[self   sd_setImageWithURL:placeholderImage:options:progress:completed:]

该方法作为sd_setImageWithURL接口的最终入口,提供了多种参数。

B.代码分析:

操作的管理:

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
     
    // 取消当前下载操作
    [self sd_cancelCurrentImageLoad];

    // 动态添加属性
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    // 如果选项非SDWebImageDelayPlaceholder
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            // 设置占位图
            self.image = placeholder;
        });
    }



    if (url.absoluteString.length > 0) {

        // check if activityView is enabled or not
        if ([self showActivityIndicatorView]) {
             // 显示 下载转圈
            [self addActivityIndicator];
       }

        __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"];
    } else {
        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);
            }
       });
    }
}

[self sd_cancelCurrentImageLoad];取消当前的下载操作,它表明 SDWebImage 管理操作的方法:
SDWebImage所有的操作实际都是通过一个 operationDictionary 的字典管理,这个字典是动态添加到 UIView 上的一个属性,因为这个operationDictionary 需要在UIButtonUIImageView 上重用,所以需要添加到它们的根类上。

这行代码是要保证没有当前正在进行的异步下载操作, 不会与即将进行的操作发生冲突, 它会调用:

// UIImageView+WebCache
// sd_cancelCurrentImageLoad #1
[self  sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"]

这行代码会取消当前这个UIImageView的所有操作,不会影响之后进行的下载操作。

占位图的实现:

// UIImageView+WebCache
//sd_setImageWithURL:placeholderImage:options:progress:completed: #4
if (!(options & SDWebImageDelayPlaceholder)) { self.image = placeholder;
}

options中没有SDWebImageDelayPlaceholder,UIImageView添加一个占位图image.

获取图片:

 // UIImageView+WebCache
 // sd_setImageWithURL:placeholderImage:options:progress:completed: #8
if (url)

检测传入的URL是否为空,如果非空就调用全局的SDWebImageManager来获取图片:

[SDWebImageManager.sharedManager downloadImageWithURL:options:progress:completed:]

下载完成后调用(SDWebImageCompletionWithFinishedBlock)completedBlock 为 UIImageView.image 赋值, 添加上最终所需要的图片.

// UIImageView+WebCache
//sd_setImageWithURL:placeholderImage:options:progress:completed: #10

dispatch_main_sync_safe(^{
   if (!wself) return; 
   if (image) { 
      wself.image = image; 
      [wself setNeedsLayout]; 
    } 
else { 
    if ((options & SDWebImageDelayPlaceholder)) {      
         wself.image = placeholder;
          [wself setNeedsLayout]; 
      }
  } 
  if (completedBlock && finished) { 
      completedBlock(image, error, cacheType, url); 
  }
});

最后在返回 operation的同时, 也会向 operationDictionary中添加一个键值对, 来表示操作的正在进行:

// UIImageView+WebCache
// sd_setImageWithURL:placeholderImage:options:progress:completed: #28
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];

它将operation 存储到operationDictionary 中方便以后的cancel操作。

清晨.jpg

2. SDWebImageManager

这个类主要用于处理异步下载和图片缓存的类,也可以直接用SDWebImageManagerdownloadImageWithURL:options:progress:completed:来直接下载图片。
可以看出这个类主要作用就是为了UIImageView+WebCacheSDWebImageDownloader, SDImageCache之间构建一个桥梁,使它们能够更好的协同工作。

A.核心代码分析:

a.SDWebImageManager

// SDWebImageManager
//- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:

if ([url isKindOfClass:NSString.class]) { 
  url = [NSURL URLWithString:(NSString *)url];
}
if (![url isKindOfClass:NSURL.class]) { 
  url = nil;
}

这块代码的功能是确定 url是否被正确传入, 如果传入参数的是 NSString类型就会被转换为NSURL, 如果转换失败, 那么url会被赋值为空, 这个下载的操作就会出错.

b. SDWebImageCombinedOperation

url被正确传入之后, 会实例一个非常奇怪的 operation, 它其实是一个遵循 SDWebImageOperation
协议的 NSObject的子类. 而这个协议也非常的简单:

@protocol SDWebImageOperation <NSObject>
 - (void)cancel;
@end

SDWebImageOperation只是看着像NSOperation但是它唯一跟NSOperation相同就是都可以响应cancel方法。调用这个类的cancel方法,会使得它持有的两个operation都被cancel

// SDWebImageCombinedOperation
// cancel #1
- (void)cancel { 
      self.cancelled = YES; 
      if (self.cacheOperation) { 
            [self.cacheOperation cancel]; 
            self.cacheOperation = nil; 
      } 
      if (self.cancelBlock) {
           self.cancelBlock(); 
          _cancelBlock = nil; 
      }
  }

既然获取了url,再通过url获取对应的key.

NSString *key = [self cacheKeyForURL:url];

接着通过key在缓存中查找一起是否下载过相同的图片

operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) { ... }];

这里调用SDImageCache的实例方法 queryDiskCacheForKey:done:来尝试在缓存中获取图片的数据,而这个方法获取的就是货真价实的NSOperation.
如果我们在缓存中查找到对应的图片,那么我们直接调用completedBlock回调块结束这一次图片的下载操作

// SDWebImageManager
// downloadImageWithURL:options:progress:completed: #47
dispatch_main_sync_safe(^{ completedBlock(image, nil, cacheType, YES, url);});

如果没有找到就调用SDWebImageDownLoader的实例方法去下载该图片:

id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { ... }];

如果这个方法返回正确的downloadedImage ,那么我们就在全局缓存中存储这个图片的数据:

 [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];

并调用completedBlockUIImageView或者UIButton添加图片。

最后我们将这个subOperationcancel 操作添加到operation.cancelBlock中,方便操作的取消

operation.cancelBlock = ^{ [subOperation cancel]; }

3. SDWebImageCache

维护了一个内存缓存和一个可选的磁盘缓存,首先看下查询图片缓存的方法:

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;

该方法主要功能是异步查询图片缓存,先在内存中查找

// SDWebImageCache
// queryDiskCacheForKey:done: #9
UIImage *image = [self imageFromMemoryCacheForKey:key];

// 内存中查找图片
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
return [self.memCache objectForKey:key];

}

imageFromMemoryCacheForKey:key 方法会在SDWebImageCache 维护的缓存memCache 中查找是否有对应的数据,而 memCache 就是一个 NSCache.

NSCache 是一个类似于 NSMutableDictionary 存储 key-value 的容器,主要有以下几个特点:

自动删除机制:当系统内存紧张时,NSCache会自动删除一些缓存对象
线程安全:从不同线程中对同一个 NSCache 对象进行增删改查时,不需要加锁
不同于 NSMutableDictionaryNSCache存储对象时不会对key进行 copy 操作

如果在内存中没有找到图片的缓存的话,就需要在磁盘中查找。

- (UIImage *)diskImageForKey:(NSString *)key {
   NSData *data = [self diskImageDataBySearchingAllPathsForKey:key]; 
   if (data) { 
      UIImage *image = [UIImage sd_imageWithData:data];
       image = [self scaledImageForKey:key image:image];
       if (self.shouldDecompressImages) {
           image = [UIImage decodedImageWithImage:image];
          } 
      return image; 
  }
 else { 
  return nil; 
  }
}

得到图片对应的NSData后还有经过:

对图片进行存储需要对url进行MD5加密计算生成相应的key值:

- (NSString *)cachedFileNameForKey:(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;
}

然后用该key作为图片文件名存储在默认路径下:

// 获取缓存路径方法(自己写的)
- (NSString*)getCachePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
if (paths.count > 0) {
NSString *path = [paths[0] stringByAppendingFormat:@"/com.hackemist.SDWebImageCache.default"];
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
[[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
}
return path;
}else{
return nil;
}
}

之前做朋友圈后台发送图片就是先将小图命名,然后根据获取到的七牛的domain和token,拼出url,接着将该url,进行md5加密,加密后存储到SDWebImage的默认存储路径下,然后在主界面显示存储的小图,后台去进行图片压缩上传任务。

 UIImage *diskImage = [self diskImageForKey:key];
  if (diskImage && self.shouldCacheImagesInMemory) {
       NSUInteger cost = SDCacheCostForImage(diskImage);
       [self.memCache setObject:diskImage forKey:key cost:cost];
   }



如果在磁盘中找到图片,就将他复制到内存中,以便下次使用。

树.jpg

4.SDWebImageDownloader

专用的并且优化的图片异步下载器,主要用来下载图片,下载放在NSOperationQueue中进行,默认maxConcurrentOperationCount为6,timeout时间为15s.

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock

该方法直接调用了下载进度回调函数:

- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
    // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
    if (url == nil) {
        if (completedBlock != nil) {
            completedBlock(nil, nil, nil, NO);
        }
        return;
    }

    dispatch_barrier_sync(self.barrierQueue, ^{
        BOOL first = NO;
        if (!self.URLCallbacks[url]) {
            self.URLCallbacks[url] = [NSMutableArray new];
            first = YES;
        }

        // Handle single download of simultaneous download request for the same URL
        NSMutableArray *callbacksForURL = self.URLCallbacks[url];
        NSMutableDictionary *callbacks = [NSMutableDictionary new];
        if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
        if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
        [callbacksForURL addObject:callbacks];
        self.URLCallbacks[url] = callbacksForURL;

        if (first) {
            createCallback();
        }
    });
}

方法会先查看这个 url是否有对应的 callback, 使用的是 downloader,持有的一个字典URLCallbacks.
如果是第一次添加回调的话, 就会执行first = YES, 这个赋值非常的关键, 因为 first不为 YES那么 HTTP 请求就不会被初始化, 图片也无法被获取.
然后, 在这个方法中会重新修正在URLCallbacks中存储的回调块.

通过dispatch_barrier_async函数提交的任务会等它前面的任务执行完才开始,然后它后面的任务必须等它执行完毕才能开始. 必须使用dispatch_queue_create创建的队列才会达到上面的效果.通过该函数来保证每张图片进度顺序。

如果是第一次添加回调块,那么就会直接运行这个createCallBack这个block,而这个block,就是我们在downloadImageWithURL:options:progress:completed: 中传入的回调块.

接着分析下NSMutableURLRequest请求:

 NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];

request发送了一个http请求,接着又初始化一个SDWebImageDownloaderOperation实例,这个实例用于请求网络资源的操作,是NSOperation的子类:

operation = [[wself.operationClass alloc] initWithRequest:request
                                                      options:options
                                                     progress:^(NSInteger receivedSize, NSInteger expectedSize) {

初始化之后,将该operation添加到NSOperationQueue中。(备注:NSOperation实例只有在调用start方法或者加入NSOperationQueue 才会执行)

[wself.downloadQueue addOperation:operation];

5.SDWebImageDownloaderOperation

这个类主要处理HTTP请求,URL连接的类,当这个类的实例被加入到队列之后,start方法被调用,start方法首先产生一个NSURLConnection,通过NSURLConnection进行图片的下载,为了确保能够处理下载的数据,需要在后台运行runloop,保证程序不被挂起.
- (void)start {
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
        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

        self.executing = YES;
        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
        self.thread = [NSThread currentThread];
    }

   [self.connection start];

    if (self.connection) {
        if (self.progressBlock) {
            self.progressBlock(0, NSURLResponseUnknownLength);
        }

        //在主线程发通知,这样也保证在主线程收到通知
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        });

        if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
           // Make sure to run the runloop in our background thread so it can process downloaded data
            // Note: we use a timeout to work around an issue with NSURLConnection cancel under iOS 5
            //       not waking up the runloop, leading to dead threads (see https://github.com/rs/SDWebImage/issues/466)
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
        }
        else {
            CFRunLoopRun();
        }

        if (!self.isFinished) {
            [self.connection cancel];
            [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
        }
    }
    else {
        if (self.completedBlock) {
            self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
        }
    }

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
    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
}

接下来这个 connection 就会开始运行:

[self.connection start];

它发出一个SDWebImageDownloadStartNotification通知,开启状态栏的请求加载转圈。同时调用NSURLConnectionDataDelegate代理

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection;

前两个代理会不停的回调 pregressBlock 来提示下载进度。

而最后一个代理方法会在图片下载完成之后调用completionBlock 来完成最后 UIImageView.image的更新,而这里调用的 progressBlockcompletionBlockcancelBlock都是在之前存储在 URLCallbacks
字典中的.

- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
    SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
    @synchronized(self) {
        // 停止 该线程 运行时
        CFRunLoopStop(CFRunLoopGetCurrent());
        self.thread = nil;
        self.connection = nil;
        // 通知停止状态栏转圈请求
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
        });
    }

    if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) {
        responseFromCached = NO;
    }

    if (completionBlock) {
        if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
            completionBlock(nil, nil, nil, YES);
        } else if (self.imageData) {
             // 进行缓存
            UIImage *image = [UIImage sd_imageWithData:self.imageData];
            NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
            image = [self scaledImageForKey:key image:image];
        
            // Do not force decoding animated GIFs
            if (!image.images) {
                // 进行解码
                if (self.shouldDecompressImages) {
                    image = [UIImage decodedImageWithImage:image];
                }
            }
            if (CGSizeEqualToSize(image.size, CGSizeZero)) {
                completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
            }
            else {
                completionBlock(image, self.imageData, nil, YES);
            }
        } else {
            completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
        }
    }
    self.completionBlock = nil;
    [self done];
}

转换处理图片和进行缓存后,将下载image赋值给控件。

四. 面试点

1、SDImageCache是怎么做数据管理的?

  1. 为什么图片要进行解压?

3.SDWebImage 在多线程下载图片时防止错乱的策略

另外解决方案:

  1. SDWebImage的主要任务就是图片的下载和缓存。为了支持这些操作,它主要使用了以下知识点:

NSOperationNSOperationQueue:操作队列是Objective-C中一种高级的并发处理方法,现在它是基于GCD来实现的。相对于GCD来说,操作队列的优点是可以取消在任务处理队列中的任务,另外在管理操作间的依赖关系方面也容易一些。对SDWebImage中我们就看到了如何使用依赖将下载顺序设置成后进先出的顺序。

开启一个后台任务。

  1. 系统级内存警告如何处理
  1. 如何播放gif图片
  1. 如何判断当前图片类型
    + (NSString *)sd_contentTypeForImageData:(NSData *)data;
    图片的十六进制数据, 的前8个字节都是一样的, 所以可以通过判断十六进制来判断图片的类型

五. 最后

送上一张自己喜欢的图片:

风景.jpeg

个人小结,有兴趣的朋友可以看一下,如果觉得不错,麻烦给个喜欢或star,若发现问题请及时反馈,谢谢!

上一篇 下一篇

猜你喜欢

热点阅读