SDWebImage是一个开源的第三方库,支持从远程服务器下载并缓存图片的功能。它具有以下功能:
- 提供UIImageView的一个分类,以支持网络图片的加载与缓存管理
- 一个异步的图片下载器
- 一个图片内存缓存+异步的磁盘缓存
- 支持GIF图片
- 支持WebP图片
- 后台图片解压缩处理
- 确保同一个URL的图片不被下载多次
- 确保虚假的URL不会被反复加载
- 确保下载及缓存时,主线程不被阻塞
UIImageView+WebCache.h
- SDWebImageCompat.h: 定义一些宏定义
- SDWebImageDefine.h:
定义关于图片操作的option(SDWebImageOptions ):图片读取操作、图片下载优先级、图片后台下载、图片下载完成展示操作等
key: -
UImageView+WebCache.h
:
最上层对外接口,对ImageVIew的扩展,提供图片设置、占位图设置、图片操作、下载过程回调、下载完成回调操作
/// 异步下载和异步缓存图片
/// @param url url
/// @param placeholder 展位图
/// @param options 图片操作,具体参考 SDWebImageOptions.h
/// @param context <#context description#>
/// @param progressBlock 图片下载回调,包括已下载大小、全部大小、url
/// @param completedBlock 图片下载回调,包括图片、下载失败错误信息、图片获取类型(SDImageCacheType:内存、硬盘、远程下载)
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
这个方法的内部是跳转调用UIView+WebCache.h
内部的方法
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;
-
UIView+WebCache.h
内部实现大致为以下省略版:
最终会调用SDWebImageManager中-loadImageWithURL: options:progress:completed
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {se'
//删除该对象之前的下载操作
[self sd_cancelImageLoadOperationWithKey:NSStringFromClass(self.class)];
self.sd_imageURL = url;
//展示占位图 placeholder
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}
SDWebImageManager *manager = [SDWebImageManager sharedManager];
if (url) {
//url不为nil,进一步获取图片处理,并返回一个NSOperation的对象
@weakify(self);
id operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
//url处理完成回调
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (completedBlock) {
completedBlock(image, data, error, cacheType, finished, url);
}
};
//展示url下载的图片
UIImage *targetImage = image;
NSData *targetData = data;
dispatch_main_async_safe(^{
[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
callCompletedBlockClojure();
});
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
//url为nil回调
dispatch_main_async_safe(^{
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
}
});
}
}
类似的,SDWebImage也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用
SDWebImageManager.h
SDWebImageManager处理图片url,获取图片后回调,其内部获取图片的过程大致为两个步骤:
- 首先会交由 SDImageCache处理, 从缓存中(
内存+本地硬盘
)查找图片 - 如果缓存中不存在图片,再交由SDWebImageDownloader下载图片
- 图片下载完成,缓存到到内存和本地磁盘中
获取图片入口
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock {
...
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
operation.manager = self;
//信号量,开启线程保护,防止多线程操作添加多个operation
dispatch_semaphore_wait(self.runningOperationsLock, DISPATCH_TIME_FOREVER);
[self.runningOperations addObject:operation];
dispatch_semaphore_signal(self.runningOperationsLock)
// 从缓存中查找 url 对应的图片
[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
return operation;
}
//缓存中查找图片
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
id imageCache = [SDImageCache sharedImageCache];;
//url作为图片的标识符key
NSString *key = [self cacheKeyForURL:url context:context];
//在缓存中查找图片是否下载过
@weakify(operation);
operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
@strongify(operation);
if (!operation || operation.isCancelled) {
// 图片下载操作被取消
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url];
[self safelyRemoveOperationFromRunning:operation];
return;
}
//图片未下载完毕,继续下载
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
// 缓存中不存在,开始下载图片
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
}
本地不存在图片,开始下载
- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(SDWebImageContext *)context
cachedImage:(nullable UIImage *)cachedImage
cachedData:(nullable NSData *)cachedData
cacheType:(SDImageCacheType)cacheType
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
id imageLoader = self.imageLoader = [SDWebImageDownloader sharedDownloader];;
// 当本地没有缓存的图片,或者需要刷新缓存时,下载新图片
BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly)
& (!cachedImage || options & SDWebImageRefreshCached)
& [imageLoader canRequestImageForURL:url];
if (shouldDownload) {
if (cachedImage && options & SDWebImageRefreshCached) {
[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
// Pass the cached image to the image loader. The image loader should check whether the remote image is equal to the cached image.
SDWebImageMutableContext *mutableContext [context mutableCopy];
mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
context = [mutableContext copy];
}
@weakify(operation);
// 1、下载图片
operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
@strongify(operation);
// 图片下载过程异常的回调
if (!operation || operation.isCancelled) { //用户取消
...
} else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
...
} else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) {
...
} else if (error) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
} else {
// 2、存储图片到缓存
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
}
//从集合中移除下载操作
if (finished) {
[self safelyRemoveOperationFromRunning:operation];
}
}];
} else if (cachedImage) {
[self callCompletionBlockForOperation:operation 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
[self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}
存储图片到缓存中
- (void)callStoreCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(SDWebImageContext *)context
downloadedImage:(nullable UIImage *)downloadedImage
downloadedData:(nullable NSData *)downloadedData
finished:(BOOL)finished
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// 图片缓存key
SDWebImageMutableContext *originContext = [context mutableCopy];
NSString *key = [self cacheKeyForURL:url context:originContext];
// 存储图片
[self storeImage:downloadedImage imageData:downloadedData forKey:key cacheType:SDImageCacheTypeAll options:options context:context completion:^{
...
}];
}
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)data
forKey:(nullable NSString *)key
cacheType:(SDImageCacheType)cacheType
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
completion:(nullable SDWebImageNoParamsBlock)completion {
id imageCache = [SDImageCache sharedImageCache];
[imageCache storeImage:image imageData:data forKey:key cacheType:cacheType completion:^{
if (completion) {
completion();
}
}];
}
"SDWebImageError.h
包含定义了错误码枚举的文件:
typedef NS_ERROR_ENUM(SDWebImageErrorDomain, SDWebImageError) {}
图片缓存读取、存储
SDImageCache + SDMemoryCache + SDDiskCache
SDMemoryCache初始化:
- 创建
串行队列
dispatch_queue_t,用于异步存储、读取本地磁盘图片
,避免阻塞主线程,同时保证图片资源访问的线程安全 - SDMemoryCache(继承自NSCache),
缓存内存图片
,方便下次快速访问相同图片 - SDDiskCache缓存本地磁盘图片
- 注册消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
图片读取、存储:
默认情况下包括两个过程,可根据需求进行配置:
- 内存图片读取、存储
-
本地磁盘图片
读取、存储,异步执行
//存储图片
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toMemory:(BOOL)toMemory
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
// 1、存储到内存中
if (toMemory) {
NSUInteger cost = image.sd_memoryCost;
[self.memoryCache setObject:image forKey:key cost:cost];
}
// 2、异步存储到本地磁盘
if (toDisk) {
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
[self _storeImageDataToDisk:imageData forKey:key];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
if (completionBlock) {
completionBlock();
}
}
}
//读取图片
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
// 1、首先检查内存中是否存在相应图片
UIImage *image;
if (queryCacheType != SDImageCacheTypeDisk) {
image = [self imageFromMemoryCacheForKey:key];
}
BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}
// 2、检查磁盘中会否存在图片
NSOperation *operation = [NSOperation new];
BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
(!image && options & SDImageCacheQueryDiskDataSync));
void(^queryDiskBlock)(void) = ^{
@autoreleasepool {
//从磁盘中查找图片数据
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
SDImageCacheType cacheType = SDImageCacheTypeNone;
/*
内存中存在图片,则取内存图片;
内存中不存在,则取磁盘图片,同时添加到内存中,方便下次快速访问此图片
*/
if (image) {
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
cacheType = SDImageCacheTypeDisk;
diskImage = [self diskImageForKey:key data:diskData options:options context:context];
if (diskImage) {
//图片保存在内存中
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
}
}
if (doneBlock) {
if (shouldQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
}
}
};
// 在ioQueue中进行查询以保持IO安全
if (shouldQueryDiskSync) {
dispatch_sync(self.ioQueue, queryDiskBlock);
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation;
}
内存图片读取、存储:
由于NSCahe类的缓存释放时间完全由系统管理,我们无法得知NSCahe缓存的释放时间,这样可能在我们需要这个缓存的时候,系统却已经把这个缓存给释放了。因此,子类SDMemoryCache初始化时维护了一个引用表 NSMapTable
,强引用key,弱引用value,用来在需要某个缓存的时候,确定这个缓存不会被释放。同时使用信号量保证多线程安全访问引用表NSMapTable
。
self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
self.weakCacheLock = dispatch_semaphore_create(1);
具体实现过程
- SDMemoryCache重写了NSCache的核心方法- setObject: forKey: cost:,存储到系统管理的NSCache内存时,同时存储一份引用到引用表NSMapTable
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
[super setObject:obj forKey:key cost:g];
if (!self.config.shouldUseWeakMemoryCache) {
return;
}
if (key && obj) {
// Store weak cache
SD_LOCK(self.weakCacheLock);
[self.weakCache setObject:obj forKey:key];
SD_UNLOCK(self.weakCacheLock);
}
}
- 重载了NSCache的- objectForKey:,
1)首先在系统的NSCache里查找,因为NSCache的释放完全由系统管理,所以取值的时候很可能value已经被系统释放了。
2)如果NSCache内存里没有找到,或者已经被系统释放了,在弱引用表NSMapTable查找,如果有值,则添加到NSCache内存中,这样在下次再取值的时候,如果NSCache中的value没有被释放,我们就能直接拿来用
这样保证了每次取value的时候,就算NSCache的那一份已经释放了,我们自己存的还能拿出来用。用内存空间换取了查询时间。尽可能提高效查询效率的同时,又保证了访问过的数据不被释放
。
- (id)objectForKey:(id)key {
id obj = [super objectForKey:key];
if (key && !obj) {
// Check weak cache
SD_LOCK(self.weakCacheLock);
obj = [self.weakCache objectForKey:key];
SD_UNLOCK(self.weakCacheLock);
if (obj) {
// Sync cache
NSUInteger cost = 0;
if ([obj isKindOfClass:[UIImage class]]) {
cost = [(UIImage *)obj sd_memoryCost];
}
[super setObject:obj forKey:key cost:cost];
}
}
return obj;
}
本地磁盘图片读取、存储:
图片存储的磁盘位置
diskCachePath:沙盒->Library->Cache
NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject
图片存储的文件名称
:url md5加密后的字符串
多线程安全:SDImageCache使用串行队列
调用SDDiskCache存储、读取磁盘图片
使用NSFileManager文件管理器创建相应文件目录,将图片数据写入目录下
- (void)setData:(NSData *)data forKey:(NSString *)key {
if (![self.fileManager fileExistsAtPath:self.diskCachePath]) {
[self.fileManager createDirectoryAtPath:self.diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// 对url进行md5加密,作为图片路径名称
NSString *cachePathForKey = [self cachePathForKey:key];
//存储到本地磁盘
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
[data writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];
// 不要被备份到iClound
if (self.config.shouldDisableiCloud) {
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
- (NSData *)dataForKey:(NSString *)key {
NSParameterAssert(key);
NSString *filePath = [self cachePathForKey:key];
NSData *data = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
if (data) {
return data;
}
data = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
if (data) {
return data;
}
return nil;
}
注意:
存储在本地的图片格式为
PNG、JPG等,这是经过编码压缩后的图片数据
,不是位图
,要把它们渲染到屏幕前就需要进行解码转成位图数据(因为位图体积很大,所以磁盘缓存不会直接缓存位图数据,而是编码压缩后的PNG或JPG数据),而这个解码操作比较耗时
,iOS默认是在主线程解码,所以SDWebImage将这个过程放到子线程了
。具体的过程下面会说明。
清除缓存图片:
-
内存图片清除
:SDMemoryCache继承自NSCache,自动响应内存警告,实现缓存图片清除 -
本地磁盘图片清除
:SDImageCache注册了通知监听,当应用结束运行时,使用UIApplication的如下方法,使用应用继续运行,调用SDDiskCache删除过期文件方法清除磁盘缓存图片
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void(^ __nullable)(void))handler;
调用SDDiskMemory删除过期文件:
- 删除过期文件
- 如果磁盘图片缓存依然大于限制的大小,继续清除最旧的图片
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
[self.diskCache removeExpiredData];
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
- (void)removeExpiredData {
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
// Compute content date key to be used for tests
NSURLResourceKey cacheContentDateKey = NSURLContentModificationDateKey;
switch (self.config.diskCacheExpireType) {
case SDImageCacheConfigExpireTypeAccessDate:
cacheContentDateKey = NSURLContentAccessDateKey;
break;
case SDImageCacheConfigExpireTypeModificationDate:
cacheContentDateKey = NSURLContentModificationDateKey;
break;
case SDImageCacheConfigExpireTypeCreationDate:
cacheContentDateKey = NSURLCreationDateKey;
break;
case SDImageCacheConfigExpireTypeChangeDate:
cacheContentDateKey = NSURLAttributeModificationDateKey;
break;
default:
break;
}
NSArray *resourceKeys = @[NSURLIsDirectoryKey, cacheContentDateKey, NSURLTotalFileAllocatedSizeKey];
// This enumerator prefetches useful properties for our cache files.
NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];
NSDate *expirationDate = (self.config.maxDiskAge < 0) ? nil: [NSDate dateWithTimeIntervalSinceNow:-self.config.maxDiskAge];
NSMutableDictionary *> *cacheFiles = [NSMutableDictionary dictionary];
NSUInteger currentCacheSize = 0;
/*
枚举缓存目录中的所有文件。 这个循环有两个目的:
1.删除过期日期之前的文件。
2.存储基于大小的清理过程的文件属性。
*/
NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
for (NSURL *fileURL in fileEnumerator) {
NSError *error;
NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
// Skip directories and errors.
if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
continue;
}
// 删除过期文件
NSDate *modifiedDate = resourceValues[cacheContentDateKey];
if (expirationDate && [[modifiedDate 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[fileURL] = resourceValues;
}
for (NSURL *fileURL in urlsToDelete) {
[self.fileManager removeItemAtURL:fileURL error:nil];
}
// 如果剩余的磁盘缓存超过配置的最大大小,请执行基于大小的清理操作 我们先删除最旧的文件
NSUInteger maxDiskSize = self.config.maxDiskSize;
if (maxDiskSize > 0 && currentCacheSize > maxDiskSize) {
// Target half of our maximum cache size for this cleanup pass.
const NSUInteger desiredCacheSize = maxDiskSize / 2;
// 以最新修改时间重新排序剩余文件
NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[cacheContentDateKey] compare:obj2[cacheContentDateKey]];
}];
// Delete files until we fall below our desired cache size.
for (NSURL *fileURL in sortedFiles) {
if ([self.fileManager removeItemAtURL:fileURL error:nil]) {
NSDictionary *resourceValues = cacheFiles[fileURL];
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
}
}
图片下载
SDWebImageDownloader + SDWebImageDownloaderOperation
SDWebImageDownloader:
实现异步图片下载
,SDWebImageDownloader初始化包括:
- 下载配置SDWebImageDownloaderConfig(
下载超时时间15s、最大并发数6、FIFO的图片下载顺序
) - 线程下载队列NSOperationQueue,
防止阻塞主线程
- NSMutableDictionary哈希表管理下载操作NSOperation,以url为key,
避免同一个URL的图片下载多次
- 下载会话管理NSURLSession
图片下载最终调用- downloadImageWithURL:options:context:progress:completed:
- 信号量
dispatch_semaphore_t
开启线程保护 - 以url为key在
查询哈希表
NSMutableDictionary,查找对应的下载操作NSOperation - 若
哈希表中不存在
相应的NSOperation,就创建一个 SDWebImageDownloaderOperation
(继承自NSOperation)的下载操作 -
缓存 operation 到哈希表
中,下次查询使用,避免同一个url下载多次 - operation
添加到 NSOperationQueue 开始下载
代码大致如下:
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
//1. 信号量多线程保护
SD_LOCK(self.operationsLock);
id downloadOperationCancelToken;
//2.哈希表中以url为key获取缓存的下载操作NSOperation
NSOperation *operation = [self.URLOperations objectForKey:url];
//哈希表不存在相应下载操作Operation,或者操作已被取消,重新创建一个SDWebImageDownloaderOperation(继承NSOperation),添加到队列中开始下载,并缓存到哈希表中方便查询
if (!operation || operation.isFinished || operation.isCancelled) {
//3.创建一个Operation
operation = [self createDownloaderOperationWithUrl:url options:options context:context];
//Operation执行完毕后从哈希表URLOperations中移除
@weakify(self);
operation.completionBlock = ^{
@strongify(self);
SD_LOCK(self.operationsLock);
[self.URLOperations removeObjectForKey:url];
SD_UNLOCK(self.operationsLock);
};
//4.缓存到哈希表中
self.URLOperations[url] = operation;
// 该方法将 progressBlock 和 completedBlock 缓存到 operation 内部定义的哈希表,以便在下载的时候执行相应的回调操作
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
//5.添加下载操作operation到队列NSOperationQueue中,开始下载
[self.downloadQueue addOperation:operation];
}
SD_UNLOCK(self.operationsLock);
//返回一个包含operatin的管理实例,用于SDWebImageManager取消下载时使用
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url;
token.request = operation.request;
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}
其中创建SDWebImageDownloaderOperation的方法createDownloaderOperationWithUrl:options:context:大体为:
- 创建NSURLRequest,配置请求头allHTTPHeaderFields、缓存策略cachePolicy、cookis、请求管线等
- 创建SDWebImageDownloaderOperation
- (nullable NSOperation *)createDownloaderOperationWithUrl:(nonnull NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context {
// 1.创建NSURLRequest,进行配置
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
//配置NSURLRequest
...
// 2.创建SDWebImageDownloaderOperation
NSOperation *operation = [[SDWebImageDownloaderOperation alloc] initWithRequest:request inSession:self.session options:options context:context];
//配置SDWebImageDownloaderOperation
...
return operation;
}
上面创建的SDWebImageDownloadOperation(继承自NSOperation)添加到线程队列NSOperationQueue后开始执行对应的系统方法-start,SDWebImageDownloadOperation重载了-start实现:
SDWebImageDownloadOperation:
初始化包括:
- 线程下载队列NSOperationQueue,最大并发数1,用于
异步解码图片数据
- Bool值存储执行状态、图片下载完成状态
等等
创建 NSURLSessionDataTask,开始执行图片下载
- (void)start {
// 1、线程保护,创建NSURLSessionDataTask
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
NSURLSession *session = self.unownedSession;
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}
// 2、开始执行下载任务
[self.dataTask resume];
// 3、执行对应的 progressBlock 回调操作
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
}
图片下载过程调用NSURLSession的代理方法,一般情况下代理对象为SDWebImageDownloader,进而在持有的线程队列downloadQueue(NSOperationQueue)中查找相应NSURLDataTask的SDWebImageDownloaderOperation,交由其处理。如果代理对象SDWebImageDownloaderOperation,直接调用相应代理实现:
以下载完成的代理执行为例,过程如下,可忽略:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSOperation *dataOperation = nil;
for (NSOperation *operation in self.downloadQueue.operations) {
if ([operation respondsToSelector:@selector(dataTask)]) {
// 线程安全保护
NSURLSessionTask *operationTask;
@synchronized (operation) {
operationTask = operation.dataTask;
}
if (operationTask.taskIdentifier == task.taskIdentifier) {
dataOperation = operation;
break;
}
}
}
if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
[dataOperation URLSession:session task:task didCompleteWithError:error];
}
}
图片下载完成后,在线程队列中异步解码图片
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// 发送通知图片下载完成
...
// 图片解码
if (!error) {
NSData *imageData = [self.imageData copy];
if (imageData) {
// 异步解码图片数据
[self.coderQueue cancelAllOperations];
[self.coderQueue addOperationWithBlock:^{
//图片解码
UIImage *image = SDImageLoaderDecodeImageData(imageData, self.request.URL, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
//回调操作block,结束任务、清除sessiond索引、清除回调block索引
[self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
[self done];
}];
}
}
}
图片解码
入口:SDImageLoader.h、SDImageCacheDefine.h
提供了图片二进制数据解码为图片
的方法
SDImageLoader:
UIImage SDImageLoaderDecodeImageData(NSData *imageData, NSURL * imageURL, SDWebImageOptions options, SDWebImageContext * context)
SDImageCacheDefine:
UIImage * SDImageCacheDecodeImageData(NSData * imageData, NSString * cacheKey, SDWebImageOptions options, SDWebImageContext * context);
两者的实现基本上一致,区别只是SDImageLoader需要将imageURL转化为图片对应的标识cachekey。
它们都只是提供了图片解码的入口,实现过程分为两步:
1.二进制数据解码为图片
:具体的实现交由 SDImageCodersManager管理的解码器实现
-
图片解码为位图
:由 SDImageCoderHelper实现
UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
NSCParameterAssert(imageData);
NSCParameterAssert(imageURL);
UIImage *image;
NSString *cacheKey = imageURL.absoluteString;
BOOL decodeFirstFrame = NO;
CGFloat scale = 1;
BOOL shouldScaleDown = NO;
SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2];
mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame);
mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale);
mutableCoderOptions[SDImageCoderWebImageContext] = context;
SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
// Grab the image coder
id imageCoder = [SDImageCodersManager sharedManager];
if (!decodeFirstFrame) {
// check whether we should use `SDAnimatedImage`
Class animatedImageClass = context[SDWebImageContextAnimatedImageClass];
if ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)]) {
image = [[animatedImageClass alloc] initWithData:imageData scale:scale options:coderOptions];
if (image) {
// Preload frames if supported
if (options & SDWebImagePreloadAllFrames && [image respondsToSelector:@selector(preloadAllFrames)]) {
[((id)image) preloadAllFrames];
}
} else {
// Check image class matching
if (options & SDWebImageMatchAnimatedImageClass) {
return nil;
}
}
}
}
// 交由 SDImageCodersManager 进行图片解码
if (!image) {
image = [imageCoder decodedImageWithData:imageData options:coderOptions];
}
//是否需要解码为位图
if (image) {
BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage);
if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)]) {
shouldDecode = NO;
} else if (image.sd_isAnimated) {
shouldDecode = NO;
}
if (shouldDecode) {
//转化为位图
image = [SDImageCoderHelper decodedImageWithImage:image];
}
}
return image;
}
管理:SDImageCodersManager
目前该管理默认提供三种解码器进行二进制数据解码,也可以自定义解码器添加到该类中:
-
SDImageIOCoder
:其他图片类型 -
SDImageGIFCoder
(继承自SDImageIOAnimatedCoder):解码gif图片 -
SDImageAPNGCoder
(继承自SDImageIOAnimatedCoder):解码png图片
当调用SDImageCodersManager进行解码时,根据图片类型,选用对应的解码器
图片类型
通过图片数据的第一字节
、数据大小
以及特定标识符
判断图片类型:
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
if (!data) {
return SDImageFormatUndefined;
}
// File signatures table: http://www.garykessler.net/library/file_sigs.html
uint8_t c;
[data getBytes:&c length:1];
switch (c) {
case 0xFF:
return SDImageFormatJPEG;
case 0x89:
return SDImageFormatPNG;
case 0x47:
return SDImageFormatGIF;
case 0x49:
case 0x4D:
return SDImageFormatTIFF;
case 0x52: {
if (data.length >= 12) {
//RIFF....WEBP
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return SDImageFormatWebP;
}
}
break;
}
case 0x00: {
if (data.length >= 12) {
//....ftypheic ....ftypheix ....ftyphevc ....ftyphevx
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
if ([testString isEqualToString:@"ftypheic"]
|| [testString isEqualToString:@"ftypheix"]
|| [testString isEqualToString:@"ftyphevc"]
|| [testString isEqualToString:@"ftyphevx"]) {
return SDImageFormatHEIC;
}
//....ftypmif1 ....ftypmsf1
if ([testString isEqualToString:@"ftypmif1"] || [testString isEqualToString:@"ftypmsf1"]) {
return SDImageFormatHEIF;
}
}
break;
}
case 0x25: {
if (data.length >= 4) {
//%PDF
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(1, 3)] encoding:NSASCIIStringEncoding];
if ([testString isEqualToString:@"PDF"]) {
return SDImageFormatPDF;
}
}
}
case 0x3C: {
if (data.length > 100) {
// Check end with SVG tag
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(data.length - 100, 100)] encoding:NSASCIIStringEncoding];
if ([testString containsString:kSVGTagEnd]) {
return SDImageFormatSVG;
}
}
}
}
return SDImageFormatUndefined;
}
二进制图片数据解码为图片
二进制图片解码最终实现都是调用 SDImageIOAnimatedCoder 内部实现方法:
解码图片第一帧,或者全部帧数
- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
if (!data) { return nil; }
CGFloat scale = 1;
CGSize thumbnailSize = CGSizeZero;
BOOL preserveAspectRatio = YES;
//创建图片数据源
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
if (!source) { return nil; }
size_t count = CGImageSourceGetCount(source);
UIImage *animatedImage;
BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
if (decodeFirstFrame || count <= 1) {
//解码图片第一帧
animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
} else {
//解码图片,遍历图片所有帧数,并合并动态图
NSMutableArray *frames = [NSMutableArray array];
for (size_t i = 0; i < count; i++) {
UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
if (!image) { continue; }
NSTimeInterval duration = [self.class frameDurationAtIndex:i source:source];
SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
[frames addObject:frame];
}
NSUInteger loopCount = [self.class imageLoopCountWithSource:source];
animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
animatedImage.sd_imageLoopCount = loopCount;
}
animatedImage.sd_imageFormat = self.class.imageFormat;
CFRelease(source);
return animatedImage;
}
创建图片源的某一帧的图片
+ (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize options:(NSDictionary *)options {
// Some options need to pass to `CGImageSourceCopyPropertiesAtIndex` before `CGImageSourceCreateImageAtIndex`, or ImageIO will ignore them because they parse once :)
// Parse the image properties
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
NSUInteger pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] unsignedIntegerValue];
NSUInteger pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] unsignedIntegerValue];
CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)[properties[(__bridge NSString *)kCGImagePropertyOrientation] unsignedIntegerValue];
if (!exifOrientation) {
exifOrientation = kCGImagePropertyOrientationUp;
}
NSMutableDictionary *decodingOptions = options ? [NSMutableDictionary dictionaryWithDictionary:options] : [NSMutableDictionary dictionary];
...
//根据图片进行图片进行尺寸、像素分辨率设置
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(source, index, (__bridge CFDictionaryRef)[decodingOptions copy]);
if (!imageRef) { return nil; }
UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation];
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:imageOrientation];
CGImageRelease(imageRef);
return image;
}
图片解码为位图
为什么要对UIImage进行解码呢?难道不能直接使用吗?
当UIImage对象赋值给UIImageView即在屏幕上渲染图像时,首先需要对其进行解码,但这是由Core Animation在主队列上发生的。当在主线程调用了大量的图片赋值后,就会产生卡顿了。
为了解决这个问题SDWebImage将图片解码为位图
的操作放在异步线程实现
,虽然需要消耗额外内存,但不会阻塞主线程。
简单解码图片:
- 已经解码、动态图、矢量图都不需要解码
- 创建位图画布
- 将图片数据绘制到画布上
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
//已经解码、动态图、矢量图都不需要解码
if (image == nil || image.sd_isDecoded || image.sd_isAnimated || image.sd_isVector) {
return image;
}
//解码图片为位图
CGImageRef imageRef = [self CGImageCreateDecoded:image.CGImage];
if (!imageRef) { return image; }
//生成uiimage,并返回图片
UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(imageRef);
SDImageCopyAssociatedObject(image, decodedImage);
decodedImage.sd_isDecoded = YES;
return decodedImage;
}
+ (CGImageRef)CGImageCreateDecoded:(CGImageRef)cgImage {
return [self CGImageCreateDecoded:cgImage orientation:kCGImagePropertyOrientationUp];
}
+ (CGImageRef)CGImageCreateDecoded:(CGImageRef)cgImage orientation:(CGImagePropertyOrientation)orientation {
if (!cgImage) {
return NULL;
}
//根据需要的图片方向,设置图片宽搞
size_t width = CGImageGetWidth(cgImage);
size_t height = CGImageGetHeight(cgImage);
if (width == 0 || height == 0) return NULL;
size_t newWidth;
size_t newHeight;
switch (orientation) {
case kCGImagePropertyOrientationLeft:
case kCGImagePropertyOrientationLeftMirrored:
case kCGImagePropertyOrientationRight:
case kCGImagePropertyOrientationRightMirrored: {
// These orientation should swap width & height
newWidth = height;
newHeight = width;
}
break;
default: {
newWidth = width;
newHeight = height;
}
break;
}
//创建没有透明因素的位图画布上下文
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage);
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
//创建位图绘制上下文
CGContextRef context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, 0, [self colorSpaceGetDeviceRGB], bitmapInfo);
if (!context) {
return NULL;
}
// 根据方向,对位图进行调整
CGAffineTransform transform = SDCGContextTransformFromOrientation(orientation, CGSizeMake(newWidth, newHeight));
CGContextConcatCTM(context, transform);
//将图片数据绘制到画布上
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); // The rect is bounding box of CGImage, don't swap width & height
//生成位图
CGImageRef newImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
return newImageRef;
}
解码图片,支持压缩图片(默认图片最大不得超过60M
)。
原理: 首先定义一个大小固定的方块,然后把原图按照方块的大小进行分割,最后把每个方块中的数据画到目标画布上,这样就能得到目标图像了。接下来我们做出相信的解释。
有几个点需要清楚:
- 图像在iOS设备上是以像素为单位显示的
- 每一个像素由RGBA组成,即R(红色)G(绿色)B(蓝色)A(透明度)4个模块,每个模块由8bit组成(16进制00~FF),所以每个像素大小 = 8bit * 4 = 32bit = 4字节
+ (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image limitBytes:(NSUInteger)bytes;
- 检查图像应不应该压缩,原则是:如果图像大于目标尺寸才需要压缩,否则调用 -decodedImageWithImage返回位图
if (![self shouldDecodeImage:image]) {
return image;
}
if (![self shouldScaleDownImage:image limitBytes:bytes]) {
return [self decodedImageWithImage:image];
}
+ (BOOL)shouldScaleDownImage:(nonnull UIImage *)image limitBytes:(NSUInteger)bytes {
BOOL shouldScaleDown = YES;
CGImageRef sourceImageRef = image.CGImage;
CGSize sourceResolution = CGSizeZero;
sourceResolution.width = CGImageGetWidth(sourceImageRef);
sourceResolution.height = CGImageGetHeight(sourceImageRef);
//图片的像素总数 = 长 * 宽
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
if (sourceTotalPixels <= 0) { return NO; }
//压缩的图片像素大小
CGFloat destTotalPixels = bytes = 0 ? bytes : kDestImageLimitBytes;
destTotalPixels = bytes / kBytesPerPixel;
if (destTotalPixels <= kPixelsPerMB) { return NO; }
//图片的像素总数 vs 压缩的图片像素大小
float imageScale = destTotalPixels / sourceTotalPixels;
if (imageScale < 1) {
shouldScaleDown = YES;
} else {
shouldScaleDown = NO;
}
return shouldScaleDown;
}
2.目标像素 destResolution
CGFloat destTotalPixels;
if (bytes == 0) {
bytes = kDestImageLimitBytes;
}
destTotalPixels = bytes / kBytesPerPixel;
其中 kBytesPerPixel
为常量:每M的字节数
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f
static CGFloat kDestImageLimitBytes = 60.f * kBytesPerMB;
- 原图分辨率sourceResolution、原图总像素 sourceResolution
CGImageRef sourceImageRef = image.CGImage;
CGSize sourceResolution = CGSizeZero;
sourceResolution.width = CGImageGetWidth(sourceImageRef);
sourceResolution.height = CGImageGetHeight(sourceImageRef);
CGFloat sourceTotalPixels = sourceResolution.width * sourceResolution.height;
4.计算图压缩比imageScale、目标图标分辨率destResolution
CGFloat imageScale = sqrt(destTotalPixels / sourceTotalPixels);
CGSize destResolution = CGSizeZero;
destResolution.width = (int)(sourceResolution.width * imageScale);
destResolution.height = (int)(sourceResolution.height * imageScale);
5.计算第一个原图方块 sourceTile,这个方块的宽度同原图一样,高度根据方块容量计算
容量 = 目标总像素 / 3
CGFloat tileTotalPixels = destTotalPixels / 3;
CGRect sourceTile = CGRectZero;
sourceTile.size.width = sourceResolution.width;
sourceTile.size.height = (int)(tileTotalPixels / sourceTile.size.width );
sourceTile.origin.x = 0.0f;
6.计算目标图像方块 destTile
CGRect destTile;
destTile.size.width = destResolution.width;
destTile.size.height = sourceTile.size.height * imageScale;
destTile.origin.x = 0.0f;
7.计算原图像块与目标方块重叠的像素大小 sourceSeemOverlap
// 重叠的像素数量
static const CGFloat kDestSeemOverlap = 2.0f;
float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
8.计算原图像需要被分割成多少个方块 iterations:
原图分辨率高度 ➗ 原图块高度,无法整除,加1处理
int iterations = (int)( sourceResolution.height / sourceTile.size.height );
int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
if(remainder) { iterations++; }
9.创建位图上下文 destContext,并设置图形上下文的插值质量级别
destContext = CGBitmapContextCreate(NULL,
destResolution.width,
destResolution.height,
kBitsPerComponent,
0,
colorspaceRef,
bitmapInfo);
if (destContext == NULL) {
return image;
}
CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
10.根据重叠像素计算原图方块的大小后,获取原图中该方块内的数据,把该数据写入到相对应的目标方块中
float sourceTileHeightMinusOverlap = sourceTile.size.height;
sourceTile.size.height += sourceSeemOverlap;
destTile.size.height += kDestSeemOverlap;
for( int y = 0; y < iterations; ++y ) {
@autoreleasepool {
sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
if( y == iterations - 1 && remainder ) {
float dify = destTile.size.height;
destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
dify -= destTile.size.height;
destTile.origin.y += dify;
}
CGContextDrawImage( destContext, destTile, sourceTileImageRef );
CGImageRelease( sourceTileImageRef );
}
}
11.返回目标图像
CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
CGContextRelease(destContext);
if (destImageRef == NULL) { return image; }
UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(destImageRef);
if (destImage == nil) { return image; }
SDImageCopyAssociatedObject(image, destImage);
destImage.sd_isDecoded = YES;
return destImage;