上个月通过观看其他人的博客,查阅网络网络资料,简单写了一些SDWebimage源码的解析,当我看完AFNetworking觉得应该再重新在按照学习AFN的模式学习下SDWebimage,下面我们开始对SDWebimage的学习。
首先看下文件目录
在这个文件目录中我们发现最多的是带“+”的类名,很显然他们都是在其基础上添加的category,然后里面有cache这个是跟缓存有关的吧,operation结尾的 只有.h文件,是SDWebimage中的多线程的标示,manager大多数的框架的核心代码救灾这个manager中。
我们先从最熟悉的UIImageView 开始,然后逐步过渡到manager类
1、UIImageView+WebCache
在该类的.h文件中,给imageView添加图片的方法
- (void)sd_setImageWithURL:(NSURL *)url;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
- (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
以上这些方法最根本的都会调用到一个私有方法- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock后面在.m文件中会重点介绍这个方法。
下面我们来看看核心代码的实现
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
//将原来的operation停止
[self sd_cancelCurrentImageLoad];
//给UIImageView关联要下载的图片的url属性
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//options:该请求所要遵循的选项
//在还没发送请求获取网络端图片之前(即网络端的image还没加载),如果options中有SDWebImageDelayPlaceholder这一选项,就不给image赋值,如果没有这一项,那么就给image赋值placeholder。
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self.image = placeholder;
});
}
//存在url
if (url) {
// check if activityView is enabled or not
//是否添加菊花view
if ([self showActivityIndicatorView]) {
[self addActivityIndicator];
}
//下载图片的方法
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
//停止转菊花
[wself removeActivityIndicator];
if (!wself) return;
dispatch_main_sync_safe(^{
//主线程同步执行
if (!wself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{//有图片,并且需要对图片进行处理,那么就调用这个block
completedBlock(image, error, cacheType, url);
return;
}
else if (image) {
//设置图片
wself.image = image;
//刷新
[wself setNeedsLayout];
} else {
//如果没有图片,需要加载图片信息
if ((options & SDWebImageDelayPlaceholder)) {
wself.image = placeholder;
[wself setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
//将改下载的operation添加到operation的字典中
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
} else { //如果url为,就抛出错误
dispatch_main_async_safe(^{
[self removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
这个方法分为三步:(1)将原来的operation停止(具体原因后面解释)(2)设置placeholder(3)下载图片
在第三步中下载,下载图片的方法会返回一个id SDWebImageOperation类型的operation,将这个operation添加到以“UIImageViewImageLoad”为key的字典中 具体 这个operation是个什么东东(猜想大概和NSOperation多少有点关系)。下一篇我们再来看这个方法,我们先把这个.m文件搞定
在.h文件中还有一些其他方法
还有一些其他方法:
加载一组图片
- (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs;
取消当前图片加载
- (void)sd_cancelCurrentImageLoad;
取消当前一组动画的加载
- (void)sd_cancelCurrentAnimationImagesLoad;
是否显示小菊花
- (void)setShowActivityIndicatorView:(BOOL)show;
小菊花的样式
- (void)setIndicatorStyle:(UIActivityIndicatorViewStyle)style;
我们来看看这些方法是干什么的
- (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs {
//取消当前正在执行的operation
[self sd_cancelCurrentAnimationImagesLoad];
__weak __typeof(self)wself = self;
NSMutableArray *operationsArray = [[NSMutableArray alloc] init];
//循环去下载这些图片然后将返回的每个operation放在operationsArray数组中,最后放在大字典里
for (NSURL *logoImageURL in arrayOfURLs) {
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:logoImageURL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!wself) return;
dispatch_main_sync_safe(^{
__strong UIImageView *sself = wself;
[sself stopAnimating];
if (sself && image) {
NSMutableArray *currentImages = [[sself animationImages] mutableCopy];
if (!currentImages) {
currentImages = [[NSMutableArray alloc] init];
}
[currentImages addObject:image];
sself.animationImages = currentImages;
[sself setNeedsLayout];
}
[sself startAnimating];
});
}];
[operationsArray addObject:operation];
}
[self sd_setImageLoadOperation:[NSArray arrayWithArray:operationsArray] forKey:@"UIImageViewAnimationImages"];
}
发现还是最后调用的下载图片的那个方法,只不过这里是多张图片,使用循环方式去下载的。
下面这一坨代码又是干什么用的呢,熟悉category的哦那同学一看就应该知道吧。平时我们是对一个类进行扩展的时候,一般是添加方法,不添加属性。这里就用到了runtime的两个方法:objc_getAssociatedObject和objc_setAssociatedObject,将一个属性跟这个类进行关联,这两个函数百度上解释额很清楚我就不在这里解释了,你看懂了这两个方法就知道下面的代码时干什么的了,平时写代码的时候也能用到,能有效提升逼格。
- (UIActivityIndicatorView *)activityIndicator {
return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
}
- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
}
- (void)setShowActivityIndicatorView:(BOOL)show{
objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, [NSNumber numberWithBool:show], OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)showActivityIndicatorView{
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
}
- (void)setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
}
- (int)getIndicatorStyle{
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
}
最后两个方法一个是添加菊花、一个是移除菊花。注意在添加菊花中是通过代码在activityIndicator上添加约束,这个大家就自行搜索吧。
最后说两个宏
如果是主线程之间就直接调用block,如果不是就在主线程中使用同步函数调用block
#define dispatch_main_sync_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_sync(dispatch_get_main_queue(), block);\
}
如果是主线程之间就直接调用block,如果不是就在主线程中使用异步函数调用block
#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
到此为止,这个类中的文件中方法、属性的解析都出来了。遗留两个问题,一个是核心方法中刚开始取消操作的原因,以及核心方法中的下载图片的方法的原理,我们在下一篇中重点介绍。