iOS面试题+基础知识源码

SDWebImage源码解析

2020-09-17  本文已影响0人  vicentwyh

SDWebImage是一个开源的第三方库,支持从远程服务器下载并缓存图片的功能。它具有以下功能:

UIImageView+WebCache.h

  1. SDWebImageCompat.h: 定义一些宏定义
  2. SDWebImageDefine.h:
    定义关于图片操作的option(SDWebImageOptions ):图片读取操作、图片下载优先级、图片后台下载、图片下载完成展示操作等
    key:
  3. 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;
  1. 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 <SDWebImageOperation> 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+WebCacheMKAnnotationView+WebCache,方便使用

SDWebImageManager.h

SDWebImageManager处理图片url,获取图片后回调,其内部获取图片的过程大致为两个步骤:

获取图片入口

- (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<SDImageCache> 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<SDImageLoader> 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<SDImageCache> 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初始化:

图片读取、存储:

默认情况下包括两个过程,可根据需求进行配置:

//存储图片
- (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);

具体实现过程

  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);
    }
}
  1. 重载了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将这个过程放到子线程了。具体的过程下面会说明。

清除缓存图片:

- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void(^ __nullable)(void))handler;

调用SDDiskMemory删除过期文件:

  1. 删除过期文件
  2. 如果磁盘图片缓存依然大于限制的大小,继续清除最旧的图片
- (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<NSString *> *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<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
    NSUInteger currentCacheSize = 0;
    
     /*
        枚举缓存目录中的所有文件。 这个循环有两个目的:
        1.删除过期日期之前的文件。
        2.存储基于大小的清理过程的文件属性。
    */
    NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
    for (NSURL *fileURL in fileEnumerator) {
        NSError *error;
        NSDictionary<NSString *, id> *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<NSURL *> *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<NSString *, id> *resourceValues = cacheFiles[fileURL];
                NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
                
                if (currentCacheSize < desiredCacheSize) {
                    break;
                }
            }
        }
    }
}


图片下载

SDWebImageDownloader + SDWebImageDownloaderOperation

SDWebImageDownloader:

实现异步图片下载,SDWebImageDownloader初始化包括:

图片下载最终调用- downloadImageWithURL:options:context:progress:completed:

  1. 信号量dispatch_semaphore_t开启线程保护
  2. 以url为key在查询哈希表NSMutableDictionary,查找对应的下载操作NSOperation
  3. 哈希表中不存在相应的NSOperation,就创建一个 SDWebImageDownloaderOperation(继承自NSOperation)的下载操作
  4. 缓存 operation 到哈希表中,下次查询使用,避免同一个url下载多次
  5. 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<SDWebImageDownloaderOperation> *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:大体为:

  1. 创建NSURLRequest,配置请求头allHTTPHeaderFields、缓存策略cachePolicy、cookis、请求管线等
  2. 创建SDWebImageDownloaderOperation
- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url
                                                                                  options:(SDWebImageDownloaderOptions)options
                                                                                  context:(nullable SDWebImageContext *)context {

    // 1.创建NSURLRequest,进行配置
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    //配置NSURLRequest
    ...

    // 2.创建SDWebImageDownloaderOperation
    NSOperation<SDWebImageDownloaderOperation> *operation = [[SDWebImageDownloaderOperation alloc] initWithRequest:request inSession:self.session options:options context:context];
    //配置SDWebImageDownloaderOperation
    ...

    return operation;
}

上面创建的SDWebImageDownloadOperation(继承自NSOperation)添加到线程队列NSOperationQueue后开始执行对应的系统方法-start,SDWebImageDownloadOperation重载了-start实现:

SDWebImageDownloadOperation:

初始化包括:

创建 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<SDWebImageDownloaderOperation> *dataOperation = nil;
    for (NSOperation<SDWebImageDownloaderOperation> *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管理的解码器实现

  1. 图片解码为位图:由 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<SDImageCoder> 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<SDAnimatedImage>)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
目前该管理默认提供三种解码器进行二进制数据解码,也可以自定义解码器添加到该类中:

当调用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<SDImageFrame *> *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将图片解码为位图的操作放在异步线程实现,虽然需要消耗额外内存,但不会阻塞主线程。

简单解码图片:

  1. 已经解码、动态图、矢量图都不需要解码
  2. 创建位图画布
  3. 将图片数据绘制到画布上
+ (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)。

原理: 首先定义一个大小固定的方块,然后把原图按照方块的大小进行分割,最后把每个方块中的数据画到目标画布上,这样就能得到目标图像了。接下来我们做出相信的解释

有几个点需要清楚:

+ (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image limitBytes:(NSUInteger)bytes;
  1. 检查图像应不应该压缩,原则是:如果图像大于目标尺寸才需要压缩,否则调用 -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;
  1. 原图分辨率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;
上一篇下一篇

猜你喜欢

热点阅读