在开发项目的过程当中会用到不少第三方库,好比AFNetWorking,SDWebImage,FMDB等,但一直都没去好好的研究一下,最近恰好项目不是太紧,闲下来能够给本身充充电,先研究一下SDWebImage的底层实现,源码地址:SDWebImage
先介绍一下SDWebImage,咱们使用较多的是它提供的UIImageView分类,支持从远程服务器下载并缓存图片。自从iOS5.0开始,NSURLCache也能够处理磁盘缓存,那么SDWebImage的优点在哪?首先NSURLCache是缓存原始数据(raw data)到磁盘或内存,所以每次使用的时候须要将原始数据转换成具体的对象,如UIImage等,这会致使额外的数据解析以及内存占用等,而SDWebImage则是缓存UIImage对象在内存,缓存在NSCache中,同时直接保存压缩过的图片到磁盘中;还有一个问题是当你第一次在UIImageView中使用image对象的时候,图片的解码是在主线程中运行的!而SDWebImage会强制将解码操做放到子线程中。下图是SDWebImage简单的类图关系:git
下面从UIImageView的图片加载开始看起,Let's go!github
首先咱们在给UIImageView设置图片的时候会调用方法:缓存
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
其中url为远程图片的地址,而placeholder为预显示的图片。
其实还能够添加一些额外的参数,好比图片选项SDWebImageOptions服务器
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { SDWebImageRetryFailed = 1
通常使用的是SDWebImageRetryFailed | SDWebImageLowPriority,下面看看具体的函数调用:网络
- (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);//关联该view对应的图片URL /*...*/ if (url) { __weak UIImageView *wself = self;//防止retain cricle //由SDWebImageManager负责图片的获取 id operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { /*获取图片到主线层显示*/ }]; [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"]; } }
能够看出图片是从服务端、内存或者硬盘获取是由SDWebImageManager管理的,这个类有几个重要的属性:app
@property (strong, nonatomic, readwrite) SDImageCache *imageCache;//负责管理cache,涉及内存缓存和硬盘保存 @property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader;//负责从网络下载图片 @property (strong, nonatomic) NSMutableArray *runningOperations;//包含全部当前正在下载的操做对象
manager会根据URL先去imageCache中查找对应的图片,若是没有在使用downloader去下载,并在下载完成缓存图片到imageCache,接着看实现:async
- (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock { /*...*/ //根据URL生成对应的key,没有特殊处理为[url absoluteString]; NSString *key = [self cacheKeyForURL:url]; //去imageCache中寻找图片 operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) { /*...*/ //若是图片没有找到,或者采用的SDWebImageRefreshCached选项,则从网络下载 if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { dispatch_main_sync_safe(^{ //若是图片找到了,可是采用的SDWebImageRefreshCached选项,通知获取到了图片,并再次从网络下载,使NSURLCache从新刷新 completedBlock(image, nil, cacheType, YES, url); }); } /*下载选项设置*/ //使用imageDownloader开启网络下载 id subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { /*...*/ if (downloadedImage && finished) { //下载完成后,先将图片保存到imageCache中,而后主线程返回 [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk]; } dispatch_main_sync_safe(^{ if (!weakOperation.isCancelled) { completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url); } }); } } /*...*/ } else if (image) { //在cache中找到图片了,直接返回 dispatch_main_sync_safe(^{ if (!weakOperation.isCancelled) { completedBlock(image, nil, cacheType, YES, url); } }); } }]; return operation; }
下面先看downloader从网络下载的过程,下载是放在NSOperationQueue中进行的,默认maxConcurrentOperationCount为6,timeout时间为15s:函数
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock { __block SDWebImageDownloaderOperation *operation; __weak SDWebImageDownloader *wself = self; /*...*/ //防止NSURLCache和SDImageCache重复缓存 NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy :NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval]; request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies); request.HTTPShouldUsePipelining = YES; request.allHTTPHeaderFields = wself.HTTPHeaders;//设置http头部 //SDWebImageDownloaderOperation派生自NSOperation,负责图片下载工做 operation = [[SDWebImageDownloaderOperation alloc] initWithRequest:request options:options progress:^(NSInteger receivedSize, NSInteger expectedSize) {} completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {} cancelled:^{}]; operation.shouldDecompressImages = wself.shouldDecompressImages;//是否须要解码 if (wself.username && wself.password) { operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession]; } if (options & SDWebImageDownloaderHighPriority) { operation.queuePriority = NSOperationQueuePriorityHigh; } else if (options & SDWebImageDownloaderLowPriority) { operation.queuePriority = NSOperationQueuePriorityLow; } [wself.downloadQueue addOperation:operation]; if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) { // 若是下载顺序是后面添加的先运行 [wself.lastAddedOperation addDependency:operation]; wself.lastAddedOperation = operation; } }]; return operation; }
SDWebImageDownloaderOperation派生自NSOperation,经过NSURLConnection进行图片的下载,为了确保可以处理下载的数据,须要在后台运行runloop:oop
- (void)start { /*...*/ #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 //开启后台下载 if ([self shouldContinueWhenAppEntersBackground]) { __weak __typeof__ (self) wself = self; self.backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ __strong __typeof (wself) sself = wself; if (sself) { [sself cancel]; [[UIApplication sharedApplication] endBackgroundTask:sself.backgroundTaskId]; sself.backgroundTaskId = UIBackgroundTaskInvalid; } }]; } #endif self.executing = YES; self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; } [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]; }); CFRunLoopRun();//在默认模式下运行当前runlooprun,直到调用CFRunLoopStop中止运行 if (!self.isFinished) { [self.connection cancel]; [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]]; } } #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 if (self.backgroundTaskId != UIBackgroundTaskInvalid) { [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId]; self.backgroundTaskId = UIBackgroundTaskInvalid; } #endif }
下载过程当中,在代理 - (void)connection:(NSURLConnection )connection didReceiveData:(NSData )data中将接收到的数据保存到NSMutableData中,[self.imageData appendData:data],下载完成后在该线程完成图片的解码,并在完成的completionBlock中进行imageCache的缓存:post
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection { SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock; @synchronized(self) { CFRunLoopStop(CFRunLoopGetCurrent());//中止当前对runloop /*...*/ if (completionBlock) { /*...*/ 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); } } } self.completionBlock = nil; [self done]; }
后续的图片缓存能够参考:SDWebImage源码剖析(二)