浅析SDWebImage

在日常的开发过程中,如果去优雅的访问网络的图片并去管理每个工程必须要面对的问题,如果想要在工程里面提供易用、简洁、方便管理的解决方案还是很有挑战的,毕竟还要兼顾图片文件的缓存、本地持久存储以及缓存控制等方方面面的问题,那么我们作为一线的开发人员如果想要快速的在工程里面实现这样的功能,那么第三方的库是首先的选择,站在巨人的肩膀上总比我们自己再去造个轮子要简单的多。所以选择使用SDWebImage就成了我们的比较好的选择。下面就用代码调用的逻辑上去分析一下整个开源代码的内容。

简单方便的API

在使用的时候,我们引入这个库,就可以使用这个开源库了。在UIImageView+WebCache.h这个文件里面,这个库是通过分类的方式来介入的,暴露了很多的调用的方法,这里就不去一一的介绍可以直接看代码就好了。这些接口最后走的都是一个方法那就是:

- (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); if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self.image = placeholder;
});
} if (url) { // 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);
}
});
}
}
  1. 这个方法里面首先看到的是就是[self sd_cancelCurrentImageLoad];用来取消当前image其他运行的还在访问的operation,说到这里就需要介绍如何显示在imageview上去缓存绑定一个operation,看一下UIView+WebCacheOperation.h这个的实现,细心的会发现为什么这个地方怎么写的是UIView的分类,而不是UIImageView了,作者的意图并不是只关注在UIImageview,同时也考虑了以后可能会在UIView的子类里面会使用的可能性,很显然作者考虑还是很长远的,在文件里面首先看到,在imageview上通过AssociatedObject绑定了一个字典,这个字典就是用来根据key来缓存不同的opreation或者是opration数组,当然也能可以删除,或者拿到这个opration去执行cancel的操作并且移除。所以[self sd_cancelCurrentImageLoad];这个方法就是取出来绑定在imageview上的opration然后去执行cancel的动作并移除这个opration。这样做的目的是为了让每个UIImzgeview只有一个opration去操作就好了,防止造成不必要的浪费。
  2. 通过objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);给UIImageView绑定一个url。
  3. 接着就是如果调用的方法里面有添加placeholder的图,那么就展示placeholder的图片
  4. 如果url为真就开始访问图片,如果需要展示activityIndicator就在imageview上去贴一个activityIndicator
  5. 最重要的一步就是生成这个请求的operation并且处理请求回来后的操作。首先调用了SDWebImageManager.sharedManager的downloadImageWithURL...的方法,然后吧block都传进去就行了,然后在完成的block里面把请求回来的image赋值给UIImageView然后,执行一下外面传进来的complete的block。
  6. 然后把这个operation缓存到UIImageView持有的字典就行了,这个operation之所以需要放在里面缓存的一个原因和内存管理也是有一定的关系的,这样就能明确的指定这个operation被那个对象持有
  7. 最后一步,是处理url为空的情况,直接返回错就行了

在这个分析的过程中,发现最核心的部分就是如何构造生成请求的operation的方法,最核心的这个部分是在SDWebImageManager中完成的,接着来看一下SDWebImageManager是什么样子。


SDWebImageManager有三个重要的属性,delegate、imageCache、imageDownloader这个三个从名字也能体会出意思,其他的也都很简单,主要看一下如何生成operation的方法。

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead"); // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
} // Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![url isKindOfClass:NSURL.class]) {
url = nil;
} __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation; BOOL isFailedUrl = NO;
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
} if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
dispatch_main_sync_safe(^{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
});
return operation;
} @synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
NSString *key = [self cacheKeyForURL:url]; operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
if (operation.isCancelled) {
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
} return;
} if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
if (image && options & SDWebImageRefreshCached) {
dispatch_main_sync_safe(^{
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
completedBlock(image, nil, cacheType, YES, url);
});
} // download if no image or requested to refresh anyway, and download allowed by delegate
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (image && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// Do nothing if the operation was cancelled
// See #699 for more details
// if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
}
else if (error) {
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
}
}); if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost) {
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}
else {
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
} BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly); if (options & SDWebImageRefreshCached && image && !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), ^{
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
[self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
} dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
});
}
else {
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
} dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
}
} if (finished) {
@synchronized (self.runningOperations) {
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
}
}];
operation.cancelBlock = ^{
[subOperation cancel]; @synchronized (self.runningOperations) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
};
}
else if (image) {
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(image, nil, cacheType, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
else {
// Image not in cache and download disallowed by delegate
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !weakOperation.isCancelled) {
completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
}]; return operation;
}

这个方法返回的是一个SDWebImageCombinedOperation类型数据,具体的实现思路是:

  1. 生成一个SDWebImageCombinedOperation对象,给cacheOperation赋值
  2. cacheOperation是由self.imageCache的方法来实现这个,self.imageCache是首先到内存里面去查,然后能去本地缓存的文件中去查,如果内存中命中则返回一个nil的值,如果在文件里面就返回一个普通的Operation,
  3. 如果没有缓存的,那就self.imageDownloader这个去生成一个新的subOperation去请求,然后通过self.runningOperations去持有这个对象,然后请求回来之后,就进一不处理外面的回调。

    通过上面的代码看,核心的有两个部分,一个是self.imageCache查缓存,另一个是self.imageDownloader去下载图片。

接着看一下SDWebImageDownloader这个文件的实现,从名字上看就知道所有图片的的下载任务都是交给它来完成的,主要的实现是通过并发为6的队列然后和一个session来处理请求的任务

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
__block SDWebImageDownloaderOperation *operation;
__weak __typeof(self)wself = self; [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
NSTimeInterval timeoutInterval = wself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
} // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (wself.headersFilter) {
request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = wself.HTTPHeaders;
}
operation = [[wself.operationClass alloc] initWithRequest:request
inSession:self.session
options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
});
for (NSDictionary *callbacks in callbacksForURL) {
dispatch_async(dispatch_get_main_queue(), ^{
SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
if (callback) callback(receivedSize, expectedSize);
});
}
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
if (finished) {
[sself.URLCallbacks removeObjectForKey:url];
}
});
for (NSDictionary *callbacks in callbacksForURL) {
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
if (callback) callback(image, data, error, finished);
}
}
cancelled:^{
SDWebImageDownloader *sself = wself;
if (!sself) return;
dispatch_barrier_async(sself.barrierQueue, ^{
[sself.URLCallbacks removeObjectForKey:url];
});
}];
operation.shouldDecompressImages = wself.shouldDecompressImages; if (wself.urlCredential) {
operation.credential = wself.urlCredential;
} else 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) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[wself.lastAddedOperation addDependency:operation];
wself.lastAddedOperation = operation;
}
}]; return operation;
}
  1. self.URLCallbacks[url]存储回调的block
  2. SDWebImageDownloaderOperation这个是通过request以及各个block来创建的对象,并用来执行的
  3. 计入到downloadQueue的队列中去,然后去等这个URLSession的各种回调
  4. 根绝task的id来识别属于哪个SDWebImageDownloaderOperation,然后在回调给SDWebImageDownloaderOperation类里面去处理,因为这个类里面持有了刚刚构建这个对象的时候的block,然后在这些block里面又调用self.URLCallbacks储存的外面的block

    这个里面其实主要的任务都在SDWebImageDownloaderOperation里面完成,这个类里面就是标准的opreation的构造,然后把外面传进来的request生成一个task,然后放到外面传进来的session中去执行,这个类里面要实现urlsession的回调的函数,这个书要是SDWebImageDownloader希望能让opreation能够独立的处理自己的应该负责的逻辑,所以才这样设计。

刚刚说了一下网络请求图片,现在介绍一下,SDWebImage的cache图片的实现。SDImageCache文件提供各种配置选项,比如缓存的到内存还是到disk,缓存文件的数量,缓存文件的最长时间、内存中缓存的最大的数量,是否使用iCloud,是否应该压缩图片,另外就是一些读取的方法。

缓存最大的挑战是如果确定缓存文件的名字和缓存文件的遍历,在这个文件中都能看到清晰的实现,接下来来探讨一下:

首先缓存image的时候是用image的url来作为key值来传入进去,然后这个key如果把最终data存储文件,这个是个挺有意思的事情,单纯的说如果是用名字来做文件名字,行不行呢?应该也是行的,但是作者给出的方案更加优化,就是使用MD5方法去进行单向散列,确保key和value的唯一性,这点就是hash思想的提现了,如下

- (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名字的问题解决了,所有的图片都拥有了唯一的名字。接着来说说,在文件大小的控制,文件数量的控制的时候需要去遍历文件,如何去遍历文件呢,看看这个库作者的方案:

- (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock {
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES]; dispatch_async(self.ioQueue, ^{
NSUInteger fileCount = 0;
NSUInteger totalSize = 0; NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:@[NSFileSize]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL]; for (NSURL *fileURL in fileEnumerator) {
NSNumber *fileSize;
[fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
totalSize += [fileSize unsignedIntegerValue];
fileCount += 1;
} if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(fileCount, totalSize);
});
}
});
}

这里有一个self.ioQueue专门干这个读写事情,另外作者使用了NSDirectoryEnumerator来直接获取了文件的大小,然后最终累加计算出所有文件的大小。另外删除文件的代码,也是值得我们去学习的:

- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey]; // This enumerator prefetches useful properties for our cache files.
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL]; NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
NSUInteger currentCacheSize = 0; // Enumerate all of the files in the cache directory. This loop has two purposes:
//
// 1. Removing files that are older than the expiration date.
// 2. Storing file attributes for the size-based cleanup pass.
NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
for (NSURL *fileURL in fileEnumerator) {
NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL]; // Skip directories.
if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {
continue;
} // Remove files that are older than the expiration date;
NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
[urlsToDelete addObject:fileURL];
continue;
} // Store a reference to this file and account for its total size.
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
[cacheFiles setObject:resourceValues forKey:fileURL];
} for (NSURL *fileURL in urlsToDelete) {
[_fileManager removeItemAtURL:fileURL error:nil];
} // If our remaining disk cache exceeds a configured maximum size, perform a second
// size-based cleanup pass. We delete the oldest files first.
if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
// Target half of our maximum cache size for this cleanup pass.
const NSUInteger desiredCacheSize = self.maxCacheSize / 2; // Sort the remaining cache files by their last modification time (oldest first).
NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}]; // Delete files until we fall below our desired cache size.
for (NSURL *fileURL in sortedFiles) {
if ([_fileManager removeItemAtURL:fileURL error:nil]) {
NSDictionary *resourceValues = cacheFiles[fileURL];
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize -= [totalAllocatedSize unsignedIntegerValue]; if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}

先删除本地超时的文件,然后如果缓存的文件大小超出边界,就去按照时间排序,然后删除到最大规定的内存的一般。这个设计,如果能自动的去清理多余的内存那么就完美了,那么我们通过上面手段能自动的检测并清理内存呢?


其余还剩下一些文件基本都是辅助分类,帮助编码解码或者是获取data的类型或者button之类的文件,也比较简单,就不再解释了。

浅析SDWebImage的更多相关文章

  1. IOS 网络浅析-(十三 SDWebImage 实用技巧)

    IOS 网络浅析-(十三 SDWebImage 实用技巧) 首先让我描述一下为了什么而产生的实用技巧.(在TableView.CollectionView中)当用户所处环境WiFi网速不够快(不能立即 ...

  2. IOS 网络浅析-(六 网络图片获取之三方SDWebImage)

    网络图片获取是大多数app所能用到的,由于实际app开发中原生api很少用到,在这里就先不介绍了,以后有时间会给大家介绍.这篇文章会给大家介绍一个三方-SDWebImage.SDWebImage 是一 ...

  3. SDWebImage浅析

    第一部分 SDWebImage库的作用: 通过对UIImageView的类别扩展来实现异步加载替换图片的工作. 主要用到的对象: 1)UIImageView(WebCache)类别,入口封装,实现读取 ...

  4. SDWebImage源码刨根问底

    前言: SDWebImage是iOS中一款处理图片的框架, 使用它提供的方法, 一句话就能让UIImageView,自动去加载并显示网络图片,将图片缓存到内存或磁盘缓存,正好有阅读开源项目的计划,于是 ...

  5. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  6. SQL Server on Linux 理由浅析

    SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...

  7. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  8. 高性能IO模型浅析

    高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking  ...

  9. netty5 HTTP协议栈浅析与实践

      一.说在前面的话 前段时间,工作上需要做一个针对视频质量的统计分析系统,各端(PC端.移动端和 WEB端)将视频质量数据放在一个 HTTP 请求中上报到服务器,服务器对数据进行解析.分拣后从不同的 ...

随机推荐

  1. 假·最大子段和 (sdutoj 4359 首尾相连)(思维)

    题目链接:http://acm.sdut.edu.cn/onlinejudge2/index.php/Home/Contest/contestproblem/cid/2736/pid/4359 具体思 ...

  2. JDK1.8源码TreeMap

    基于红黑树(Red-Black tree)的 NavigableMap 实现:键的排序由构造方法决定:自然排序,Comparator排序:非线程安全(仅改变与现有键关联的值不是结构上的修改):线程安全 ...

  3. 三、springcloud之服务调用Feign

    一.背景 项目中接口调用: Httpclient Okhttp Httpurlconnection RestTemplate 微服务提供了更简单,方便的Feign 二.Feign简介 Feign是一个 ...

  4. 04 Effective Go 高效的go语言 重点内容

    Effective Go  高效的go语言 Introduction 介绍 Examples 例子 Formatting 格式 Commentary 评论 Names 名字 Package names ...

  5. java基础78 Servlet的生命周期

    1.Servlet的生命周期 简单的解析就是: 创建servlet实例(调用构造器)---->调用init()方法---->调用service()方法----->调用destroy( ...

  6. 查找sqlserver数据库中,查询某值所表名和字段名

    有时候我们想通过一个值知道这个值来自数据库的哪个表以及哪个字段,通过一个存储过程实现的.只需要传入一个想要查找的值,即可查询出这个值所在的表和字段名. 前提是要将这个存储过程放在所查询的数据库. CR ...

  7. Python装饰器讲解

    Python装饰器讲解 定义:本质是函数,就是为其他函数添加附加功能.原则:1.不能修改被装饰的函数的源代码 2.不能修改被装饰的函数的调用方式 import time def timmer(func ...

  8. gtk+学习笔记(六)

    今天用到了滚动窗口和微调按钮,根据网上的信息,简单总结下用法. 滚动窗口只能添加一个控件到其中 scrolled=gtk_scrolled_window_new(NULL,NULL); /*创建滚动窗 ...

  9. mac上Python安装和修改Python默认路径遇到的问题

    此处例子是我使用homebrew安装了python3.6.1,建立一个符号链接,创建一个python3的命令,达到使用自己安装的python3的目的.此处不修改PATH,而是把需要添加的可执行文件或者 ...

  10. peda的官方文档说明

    peda在github上的官方文档,摘抄过来,方便查阅. 安装 git clone https://github.com/longld/peda.git ~/peda echo "sourc ...