SDWebImage5.0进行了一次架构上的改进,因为转Swift之后,一直没用到SDWebImage,所以也没怎么关注。最近有空刚好学习一下SDWebImage5.11的源码。

SDWebImage对UIButton,UIImageView,NSButton,UIView进行了拓展,并对外提供了接口。无论对UIButton,UIImageView还是NSButton调用
sd_setImageWithURL的时候,最终都会调用到UIView拓展类的sd_internalSetImageWithURL方法。前面的拓展都只是对外的接口,主要逻辑处理放在
SDWebImageManager里面。他相当于一个调度中心,如果需要缓存(读跟取),他就会调用SDImageCache,如果需要下载,就会调用SDWebImageDownloader。类似我们MVP模式下的Presenter,收到View拓展接口相关的参数后,根据不同业务传递给cache跟downloader处理,最后将处理完的数据通过block回调给接口。
最后还有一些工具,没有在流程图中画出来,这里说明一下:
Decoder:做一些编解码操作,针对不同类型的图片进行不同的操作。
Transform:从缓存或下载转换图像加载的转换器协议。
AnimatedImage:可以替代UIImageView,支持gif
Utils:存放一些枚举,Define,还有菊花器
Categories:对需要的类进行拓展,大部分是UIImage
Private:一些私人方法
直接找到
sd_internalSetImageWithURL方法,这是入口进来后第一个处理的方法,处理内容如下:a. 拿到旧的operation(任务),取消其操作,并从
SDOperationsDictionary移除。然后创建新的加载任务,并加入到SDOperationsDictionary中。b. 处理进度条,重置进度条
c. 处理菊花器
d. 创建SDWebImageManager,并调用
loadImageWithURL加载图片
我们先看sd_internalSetImageWithURL方法里面的代码
/*
*通过SDWebImageContextSetImageOperationKey拿到SDOperationsDictionary的key:validOperationKey(说白了这里就是二维字典)
*在通过validOperationKey拿到对应的Operation(任务),对任务进行取消之类的相关操作
*/
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
if (!validOperationKey) {
// pass through the operation key to downstream, which can used for tracing operation or image view class
validOperationKey = NSStringFromClass([self class]);
// 对context进行深拷贝,转为可变字典
SDWebImageMutableContext *mutableContext = [context mutableCopy];
// 将当前类对象名称装载进mutableContext
mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
// 将mutableContext转回不可变字典context
context = [mutableContext copy];
}
// 将validOperationKey存储起来
self.sd_latestOperationKey = validOperationKey;
// 如果这个key存在任务,则取消任务,且从SDOperationsDictionary移除
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
// 将url存储起来
self.sd_imageURL = url;
这段代码主要是拿到validOperationKey,并传给sd_cancelImage方法,sd_cancelImage的逻辑也很简单,通过validOperationKey,在SDOperationsDictionary里面拿到对应的任务,并取消。下面是sd_cancelImage的代码:
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
if (key) {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
id<SDWebImageOperation> operation;
// 拿到对应的任务operation
@synchronized (self) {
operation = [operationDictionary objectForKey:key];
}
if (operation) {
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
// 如果存在并遵循SDWebImageOperation代理,则取消任务
[operation cancel];
}
// 最后将任务移除
@synchronized (self) {
[operationDictionary removeObjectForKey:key];
}
}
}
}
// 是否需要延迟加载占位图
if (!(options & SDWebImageDelayPlaceholder)) {
// 主线程显示占位图
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}
if (url) {
// reset the progress
// 重置进度条
NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
if (imageProgress) {
imageProgress.totalUnitCount = 0;
imageProgress.completedUnitCount = 0;
}
#if SD_UIKIT || SD_MAC
// check and start image indicator
// 有菊花器就转菊花
[self sd_startImageIndicator];
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
// 拿到当前manager,没有就创建,有的话就将context的移除,防止循环引用
SDWebImageManager *manager = context[SDWebImageContextCustomManager];
if (!manager) {
manager = [SDWebImageManager sharedManager];
} else {
// remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager)
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextCustomManager] = nil;
context = [mutableContext copy];
}
// 对进度条进行处理,如果菊花器是进度条类型的,那就让进度条跑起来,回调进度block
SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
if (imageProgress) {
imageProgress.totalUnitCount = expectedSize;
imageProgress.completedUnitCount = receivedSize;
}
#if SD_UIKIT || SD_MAC
if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) {
double progress = 0;
if (expectedSize != 0) {
progress = (double)receivedSize / expectedSize;
}
progress = MAX(MIN(progress, 1), 0); // 0.0 - 1.0
dispatch_async(dispatch_get_main_queue(), ^{
[imageIndicator updateIndicatorProgress:progress];
});
}
#endif
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};
注意这里并没有直接调用combinedProgressBlock处理进度条,而是在下面加载图片的时候将combinedProgressBlock扔过去处理。
这里调用了SDWebImageManager的图片加载方法,将一些必要参数传递过去,接下来就是SDWebImageManager的事情了。
[manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL)
直接找到
loadImageWithURL方法,这个方法主要是对url的一些判断,context与options的预处理,内容如下:a. 先判断url的可行性
b. 对
context,options进行预处理,并放到result里面c. 调用
callCacheProcessForOperation判断是否有缓存,如果有则进入ImageCache拿到缓存数据,如果没有则进入callDownloadProcessForOperation方法进一步判断如何下载先看看这些步骤的源码,看完再看
callCacheProcessForOperation做了些什么
// 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;
}
// 可以把operation当做是一个任务,一个执行着读取图片(缓存跟加载器的组合)操作的任务
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
operation.manager = self;
BOOL isFailedUrl = NO;
// 检测当前url是否在failedURLs列表中
if (url) {
//os_unfair_lock的宏定义
// 加锁,防止多个线程对failedURLs操作,引起的数据问题
SD_LOCK(_failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url];
SD_UNLOCK(_failedURLsLock);
}
// 如果url为nil,且未设置SDWebImageRetryFailed,url在failedURLs列表中,执行失败回调
// SDWebImageRetryFailed为失败链接重试,默认是不会重试
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil";
NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL;
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] url:url];
return operation;
}
这里注释应该很清楚了,就是判断url的可行性,跟url是否在失败列表里面,如果在的,且options没有SDWebImageRetryFailed的话,就直接失败回调。值得注意的是SD的锁在iOS10以上用的是os_unfair_lock,iOS10以下用的是OSSpinLockLock(这个锁存在任务优先级问题,已经被淘汰了)
context,options进行预处理,并放到result里面// 将当前operation加入到runningOperations(正在运行的operation)
// 加锁,防止多个线程对runningOperations进行操作
SD_LOCK(_runningOperationsLock);
[self.runningOperations addObject:operation];
SD_UNLOCK(_runningOperationsLock);
// Preprocess the options and context arg to decide the final the result for manager
// 对context进行预处理,然后将处理的context跟options包装到result里面。
/* 里面对context的处理包括,SDWebImageContextImageTransformer、SDWebImageContextCacheKeyFilter、SDWebImageContextCacheSerializer。分别查看外面是否自定义这3个key的context,如果有就使用,没有就使用SD默认的。除了SDWebImageContextCacheKeyFilter(缓存url的key)默认是本身的url,其他2个都是nil
*/
SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
这里先将任务加入到正在执行的列表里面,然后再对context进行预处理,源代码是没有对options进行说明处理的,然后将context跟options放入result里面。context的处理源代码就不贴出来了,大概就是对SDWebImageContextImageTransformer、SDWebImageContextCacheKeyFilter、SDWebImageContextCacheSerializer这3个进行一个判断,看是否有自定义的传过来,没有就用默认的。
callCacheProcessForOperation的调用这里主要是判断要到哪里去取数据,ImageCache,还是去下载,接下来就进入这个方法看一下。
这里主要是判断任务是否该走缓存查询,或者直接下载。如果是缓存查询,就进入
SDImageCache里面进行缓存查询,且在此处理缓存结果的回调。否则就调用callDownloadProcessForOperation进入下一步判断。①. 拿到
imageCache,拿到缓存类型queryCacheType②. 通过
options判断,走缓存还是下载。如果走缓存,则调用SDImageCache里面的queryImageForKey(开始进入SDImageCache的逻辑);如果走下载,则调用callDownloadProcessForOperation开始下载前的一些处理。
imageCache,拿到缓存类型queryCacheType
// Grab the image cache to use
// 查看是否有传进来的自定义缓存对象,没有就用默认的imageCache
id<SDImageCache> imageCache;
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
imageCache = context[SDWebImageContextImageCache];
} else {
imageCache = self.imageCache;
}
// Get the query cache type
// 查看缓存类型,默认是all,如果有传进来的就用传进来的
SDImageCacheType queryCacheType = SDImageCacheTypeAll;
if (context[SDWebImageContextQueryCacheType]) {
queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue];
}
options,判断缓存查找,还是下载// Check whether we should query cache
// SD_OPTIONS_CONTAINS为与运算,当options为SDWebImageFromLoaderOnly时为true(或者全是1也可以)
// 注意这里是取反,也就是设置了SDWebImageFromLoaderOnly后是不走缓存,直接下载
BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
if (shouldQueryCache) {
// 拿到缓存的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) {
// Image combined operation cancelled by user
// 抛出错误
[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;
} else if (context[SDWebImageContextImageTransformer] && !cachedImage) {
// 没拿到缓存图片,且图片有经过Transformer转化,那就去查询原始图片缓存
//有机会去查询原始缓存
// Have a chance to query original cache instead of downloading
[self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
return;
}
// Continue download process
// 走下载流程
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
}];
} else {// 走下载流程
// Continue download process
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
}
这里解释一下key是怎么拿(SDWebImage的缓存key是怎么样的),逻辑在这个方法里面cacheKeyForURL,代码就不贴出来了,说一下大概逻辑。
a、
SDWebImage的context里面有个SDWebImageContextCacheKeyFilter,里面存储的是用来存放自定义key逻辑的协议,通过重写cacheKeyForURL自定义key,如果没有传SDWebImageContextCacheKeyFilter进来则使用url的string值。
b、然后通过context里面的SDWebImageContextImageThumbnailPixelSize、SDWebImageContextImagePreserveAspectRatio和SDWebImageContextImageTransformer这3个里面是否有值,如果有值就加上上面的key进行拼接,没值就直接用上面的key。
查到缓存后就是回调了,回调看代码注释,问题应该不大,要注意的是它也走了callDownloadProcessForOperation这个方法,因为options为SDWebImageRefreshCached的情况下,也是要走下载的,所以索性将找到的缓存,放到callDownloadProcessForOperation处理,而不是直接回调。
接下来看一下SDImageCache模块,看看SDWebImage是如何查询缓存的。
一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame
运行bundleinstall后出现此错误:Gem::Package::FormatError:nometadatafoundin/Users/jeanosorio/.rvm/gems/ruby-1.9.3-p286/cache/libv8-3.11.8.13-x86_64-darwin-12.gemAnerroroccurredwhileinstallinglibv8(3.11.8.13),andBundlercannotcontinue.Makesurethat`geminstalllibv8-v'3.11.8.13'`succeedsbeforebundling.我试试gemin
我正在运行Ubuntu11.10并像这样安装Ruby1.9:$sudoapt-getinstallruby1.9rubygems一切都运行良好,但ri似乎有空文档。ri告诉我文档是空的,我必须安装它们。我执行此操作是因为我读到它会有所帮助:$rdoc--all--ri现在,当我尝试打开任何文档时:$riArrayNothingknownaboutArray我搜索的其他所有内容都是一样的。 最佳答案 这个呢?apt-getinstallri1.8编辑或者试试这个:(非rvm)geminstallrdocrdoc-datardoc-da
我正在使用PostgreSQL9.1.3(x86_64-pc-linux-gnu上的PostgreSQL9.1.3,由gcc-4.6.real(Ubuntu/Linaro4.6.1-9ubuntu3)4.6.1,64位编译)和在ubuntu11.10上运行3.2.2或3.2.1。现在,我可以使用以下命令连接PostgreSQLsupostgres输入密码我可以看到postgres=#我将以下详细信息放在我的config/database.yml中并执行“railsdb”,它工作正常。开发:adapter:postgresqlencoding:utf8reconnect:falsedat
目录0专栏介绍1平面2R机器人概述2运动学建模2.1正运动学模型2.2逆运动学模型2.3机器人运动学仿真3动力学建模3.1计算动能3.2势能计算与动力学方程3.3动力学仿真0专栏介绍?附C++/Python/Matlab全套代码?课程设计、毕业设计、创新竞赛必备!详细介绍全局规划(图搜索、采样法、智能算法等);局部规划(DWA、APF等);曲线优化(贝塞尔曲线、B样条曲线等)。?详情:图解自动驾驶中的运动规划(MotionPlanning),附几十种规划算法1平面2R机器人概述如图1所示为本文的研究本体——平面2R机器人。对参数进行如下定义:机器人广义坐标
网站的日志分析,是seo优化不可忽视的一门功课,但网站越大,每天产生的日志就越大,大站一天都可以产生几个G的网站日志,如果光靠肉眼去分析,那可能看到猴年马月都看不完,因此借助网站日志分析工具去分析网站日志,那将会使网站日志分析工作变得更简单。下面推荐两款网站日志分析软件。第一款:逆火网站日志分析器逆火网站日志分析器是一款功能全面的网站服务器日志分析软件。通过分析网站的日志文件,不仅能够精准的知道网站的访问量、网站的访问来源,网站的广告点击,访客的地区统计,搜索引擎关键字查询等,还能够一次性分析多个网站的日志文件,让你轻松管理网站。逆火网站日志分析器下载地址:https://pan.baidu.
1.回顾.TransportServicepublicclassTransportServiceextendsAbstractLifecycleComponentTransportService:方法:1publicfinalTextendsTransportResponse>voidsendRequest(finalTransport.Connectionconnection,finalStringaction,finalTransportRequestrequest,finalTransportRequestOptionsoptions,TransportResponseHandlerT>
参考文章搭建文章gitte源码在线体验可以注册两个号来测试演示图:一.整体介绍 介绍SignalR一种通讯模型Hub(中心模型,或者叫集线器模型),调用这个模型写好的方法,去发送消息。 内容有: ①:Hub模型的方法介绍 ②:服务器端代码介绍 ③:前端vue3安装并调用后端方法 ④:聊天室样例整体流程:1、进入网站->调用连接SignalR的方法2、与好友发送消息->调用SignalR的自定义方法 前端通过,signalR内置方法.invoke() 去请求接口3、监听接受方法(渲染消息)通过new signalR.HubConnectionBuilder().on
一、机器人介绍 此处是基于MATLABRVC工具箱,对ABB-IRB-1200型号的微型机械臂进行正逆向运动学分析,并利Simulink工具实现对机械臂进行具有动力学参数的末端轨迹规划仿真,最后根据机械模型设计Simulink-Adams联合仿真。 图1.ABBIRB 1200尺寸参数示意图ABBIRB 1200提供的两种型号广泛适用于各作业,且两者间零部件通用,两种型号的工作范围分别为700 mm 和 900 mm,大有效负载分别为 7 kg 和5 kg。 IRB 1200 能够在狭小空间内能发挥其工作范围与性能优势,具有全新的设计、小型化的体积、高效的性能、易于集成、便捷的接
目录一.大致如下常见问题:(1)找不到程序所依赖的Qt库version`Qt_5'notfound(requiredby(2)CouldnotLoadtheQtplatformplugin"xcb"in""eventhoughitwasfound(3)打包到在不同的linux系统下,或者打包到高版本的相同系统下,运行程序时,直接提示段错误即segmentationfault,或者Illegalinstruction(coredumped)非法指令(4)ldd应用程序或者库,查看运行所依赖的库时,直接报段错误二.问题逐个分析,得出解决方法:(1)找不到程序所依赖的Qt库version`Qt_5'