您的位置:首页 > 其它

SDWebImage学习

2015-08-31 23:16 344 查看

SDWebImage学习

github托管地址:https://github.com/rs/SDWebImage

导入头文件:UIImageView+WebCache.h

使用sd_setImageWithURL:缓存图片:

1 . 方法sd_setImageWithURL:

//简单的加载url中的图片
[self.image sd_setImageWithURL:url]


2 . 方法 sd_setImageWithURL: placeholderImage:

//加载完成之前使用图片placeImage作为默认值
[self.image1 sd_setImageWithURL:url placeholderImage:placeImage];


3 . 方法sd_setImageWithURL:imagePath2 completed:

//在图片加载完成之后执行block
[self.image2 sd_setImageWithURL:imagePath2 completed:
^(UIImage *image, NSError *error,
SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(@"Do something in here...");
}];


4 . 方法sd_setImageWithURL:placeholderImage:options:

//options表示缓存方式
[self.image sd_setImageWithURL:url
placeholderImage:placeImage
options:SDWebImageRetryFailed];


options每个选项含义:

//加载失败后重试
SDWebImageRetryFailed = 1 << 0,
//UI交互期间延时下载
SDWebImageLowPriority = 1 << 1,
//只进行内存缓存
SDWebImageCacheMemoryOnly = 1 << 2,
//逐步下载,显示图像也是逐步的
SDWebImageProgressiveDownload = 1 << 3,
//刷新缓存
SDWebImageRefreshCached = 1 << 4,
//后台下载
SDWebImageContinueInBackground = 1 << 5,
//通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES
//把cookies存储在NSHTTPCookieStore
SDWebImageHandleCookies = 1 << 6,
//允许使用无效的SSL证书
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
//优先下载
SDWebImageHighPriority = 1 << 8,
//延迟显示占位符
SDWebImageDelayPlaceholder = 1 << 9,
//进行任意图形变换
SDWebImageTransformAnimatedImage = 1 << 10,


SDWebImage中我们使用较多的是它提供的UIImageView分类,支持从远程服务器下载并缓存图片。从iOS5.0开始,NSURLCache也可以处理磁盘缓存,那么SDWebImage有什么优势?首先,NSURLCache是缓存原始数据(raw data)到磁盘或内存,因此每次使用的时候需要将原始数据转换成具体的对象,如UIImage等,这会导致额外的数据解析以及内存占用等,而SDWebImage则是缓存UIImage对象在内存,缓存在NSCache中,同时直接保存压缩过的图片
4000
到磁盘中;其次,当你第一次在UIImageView中使用image对象的时候,图片的解码是在主线程中运行的!而SDWebImage会强制将解码操作放到子线程中。下图是SDWebImage简单的类图关系:



图片加载调用函数:

- (void)sd_setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionBlock)completedBlock {

//取消正在下载的操作
[self sd_cancelCurrentImageLoad];
//创建对象self和对象url的关联
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
/*...*/

if (url) {
//防止循环引用
__weak UIImageView *wself = self;
//由SDWebImageManager负责图片的获取
id <SDWebImageOperation> operation =
[SDWebImageManager.sharedManager downloadImageWithURL:url
options:options
progress:progressBlock
completed:
^(UIImage *image, NSError *error, SDImageCacheType cacheType,
BOOL finished, NSURL *imageURL) {
/*获取图片到主线层显示*/
}];

[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
} else {
/*...*/
}
}


由以上函数可知图片是从服务端、内存或者硬盘获取是由SDWebImageManager管理的,这个类有几个重要的属性:

//负责管理cache,涉及内存缓存和硬盘保存
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
//负责从网络下载图片
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;


manager会根据URL先去imageCache中查找对应的图片,如果没有再使用downloader去下载,并在下载完成缓存图片到imageCache,接着看实现:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {

/*...*/
//根据URL生成对应的key,没有特殊处理为[url absoluteString];
NSString *key = [self cacheKeyForURL:url];
//去imageCache中寻找图片
operation.cacheOperation =
[self.imageCache queryDiskCacheForKey:key
done:^(UIImage *image, SDImageCacheType cacheType) {
/*...*/
//如果图片没有找到,或者采用的SDWebImageRefreshCached选项,则从网络下载
if ((!image || options & SDWebImageRefreshCached) &&
(![self.delegate respondsToSelector:@selector(
imageManager:shouldDownloadImageForURL:)]
|| [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
if (image && options & SDWebImageRefreshCached) {
dispatch_main_sync_safe(^{
//如果图片找到了,但是采用的SDWebImageRefreshCached选项,
//通知获取到了图片,并再次从网络下载,使NSURLCache重新刷新
completedBlock(image, nil, cacheType, YES, url);
});
}

/*下载选项设置...*/
//使用imageDownloader开启网络下载
id <SDWebImageOperation> subOperation =
[self.imageDownloader downloadImageWithURL:url
options:downloaderOptions
progress:progressBlock
completed:^(UIImage *downloadedImage,
NSData *data, NSError *error,
BOOL finished) {
/*...*/

if (downloadedImage && finished) {
//下载完成后,先将图片保存到imageCache中,然后主线程返回
recalculateFromImage:NO imageData:data
forKey:key toDisk:cacheOnDisk];
}

dispatch_main_sync_safe(^{
if (!weakOperation.isCancelled) {
completedBlock(downloadedImage, nil,
SDImageCacheTypeNone, finished, url);
}
});
}
}
/*...*/
else if (image) {
//在cache中找到图片了,直接返回
dispatch_main_sync_safe(^{
if (!weakOperation.isCancelled) {
completedBlock(image, nil, cacheType, YES, url);
}
});
/*...*/
}
}];

return operation;
}


下面先看downloader从网络下载的过程,下载是放在NSOperationQueue中进行的,默认maxConcurrentOperationCount为6,timeout时间为15s:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock {

__block SDWebImageDownloaderOperation *operation;
__weak SDWebImageDownloader *wself = self;

/*...*/
//防止NSURLCache和SDImageCache重复缓存
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL:url
cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ?
NSURLRequestUseProtocolCachePolicy :
NSURLRequestReloadIgnoringLocalCacheData)
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (wself.headersFilter) {
request.allHTTPHeaderFields =
wself.headersFilter(url, [wself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = wself.HTTPHeaders;
}
//SDWebImageDownloaderOperation派生自NSOperation,负责图片下载工作
operation = [[wself.operationClass alloc]
initWithRequest:request
options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize) {/*...*/}
completed:^(UIImage *image, NSData *data,
NSError *error, BOOL finished) {/*...*/}
cancelled:^{/*...*/}];
/*...*/}];

return operation;
}


SDWebImageDownloaderOperation派生自NSOperation,通过NSURLConnection进行图片的下载,为了确保能够处理下载的数据,需要在后台运行runloop:

- (void)start {
@synchronized (self) {

/*...*/
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
/*开启后台下载*/
if ([self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
self.backgroundTaskId = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;

if (sself) {
[sself cancel];

[[UIApplication sharedApplication]
endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif

self.executing = YES;
self.connection = [[NSURLConnection alloc]
initWithRequest:self.request delegate:self startImmediately:NO];
self.thread = [NSThread currentThread];
}

[self.connection start];

if (self.connection) {
if (self.progressBlock) {
self.progressBlock(0, NSURLResponseUnknownLength);
}
/*广播通知*/
[[NSNotificationCenter defaultCenter]
postNotificationName:SDWebImageDownloadStartNotification object:self];

if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
}
else {
//在默认模式下运行当前runlooprun,直到调用CFRunLoopStop停止运行
CFRunLoopRun();
}

if (!self.isFinished) {
[self.connection cancel];
[self connection:self.connection
didFailWithError:[NSError errorWithDomain:NSURLErrorDomain
code:NSURLErrorTimedOut
userInfo:
@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
}
}
else {
if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain
code:0
userInfo:
@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
}
}

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = UIBackgroundTaskInvalid;
}
#endif
}


下载过程中,在代理 - (void)connection:(NSURLConnection )connection didReceiveData:(NSData )data中将接收到的数据保存到NSMutableData中,[self.imageData appendData:data],下载完成后在该线程完成图片的解码,并在完成的completionBlock中进行imageCache的缓存:

- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
@synchronized(self) {
//停止当前的runLoop
CFRunLoopStop(CFRunLoopGetCurrent());
/*...*/
}
/*...*/
if (compl
ada5
etionBlock) {
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
completionBlock(nil, nil, nil, YES);
}
else {
UIImage *image = [UIImage sd_imageWithData:self.imageData];
NSString *key = [[SDWebImageManager sharedManager]
cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image];

if (!image.images) {
//图片解码
image = [UIImage decodedImageWithImage:image];
}
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
completionBlock(nil, nil, [NSError errorWithDomain:@"SDWebImageErrorDomain"
code:0
userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
}
else {
completionBlock(image, self.imageData, nil, YES);
}
}
}
self.completionBlock = nil;
[self done];
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  SDWebImage