浅析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. 编写高效的JavaScript程序

    作者: Addy Osmani  来源: CSDN  发布时间: 2013-01-10 14:15  阅读: 7952 次  推荐: 15   原文链接   [收藏] 英文原文:Writing Fas ...

  2. Python——脚本(calculator)

    <Python基础教程>(第二版) P123 书中原代码如下: class Calculator: def calculator(self,expression): self.value ...

  3. Hibernate5笔记8--Hibernate事务相关内容

    Hibernate事务相关内容: (1) 事务四大特性(简称ACID): (1)原子性(Atomicity) 事务中的全部操作在数据库中是不可分割的,要么全部完成,要么均不执行. (2)一致性(Con ...

  4. docker stack 部署 rabbitmq 容器

    =============================================== 2018/5/13_第1次修改                       ccb_warlock == ...

  5. mac 下安装pip

    pip是常用的Python包管理工具,类似于Java的maven.用python的同学,都离不开pip. 在新mac中想用home-brew安装pip时,遇到了一些小问题: bogon:~ wangl ...

  6. Java 泛型和类型安全的容器

    使用java SE5之前的容器的一个主要问题就是编译器允许你向容器插入不正确的类型,例如: //: holding/ApplesAndOrangesWithoutGenerics.java // Si ...

  7. MySQL学习笔记:生成时间维度表2

    实现目的: 测试: # 测试 加一秒 SECOND), INTERVAL SECOND); SECOND),'%H%i%s');# 第一秒 SECOND),'%H%i%s');# 最后一秒 SELEC ...

  8. 20155309 2016-2017-2《Java程序设计》课程总结

    预备作业1http://www.cnblogs.com/nhx19970709/p/6155580.html 第一次写博客,也是第一次用Markdown,具体流程都还不是很熟悉 预备作业2http:/ ...

  9. 浅谈ABP最佳实践

    目录 ABP概念简述 ABP在[事务操作]上的简便性 ABP在[关联查询]上的“美”和“坑” ABP的[参数验证]方式 ABP概念简述 ABP是“ASP.NET Boilerplate Project ...

  10. 【转载】Node.js 教程(菜鸟教程系列)

    很好的一篇教程:Node.js 教程 简单做下笔记 概述 简单的说 Node.js 就是运行在服务端的 JavaScript. Node.js 是一个基于Chrome JavaScript 运行时建立 ...