SDWebImage扩展二次加载图片
2016-03-08 12:28
471 查看
随着网络安全的意识加重,相信很多同学都遇到过图片二次加载的需求,即第一次请求获得图片的URL,第二次请求获得图片。在iOS中图片的加载库很多,而SDWebImage绝对是其中的翘楚,也是广大iOSer们开发时候的首选。这里简单介绍一下自己在项目中在SDWebImage基础上扩展的图片二次加载请求。为了不破坏SDWebImage原有的线程控制和加载逻辑,所以获取图片的URL的线程仍然使用继承SDWebImageOperation的方式来开展,这里取名为:SDWebImageFetchURLOperation,代码如下:
SDWebImageFetchURLOperation.h
SDWebImageFetchURLOperation.m
其实这部分代码基本属于比葫芦画瓢,就是模仿SDWebImageDownloaderOperation来写的。写好了获取图片URL的Operation,就要处理逻辑方面的东西了。这部分的逻辑主要在SDWebImageDownloader中做修改,修改思路就是在每个下载图片的operation前面都添加一个获取图片的operation,然后让下载的operation依赖于获取图片URL的operation,但是相信大家使用SDWebImage的情形大部分还是在UITabView等列表中,而且图片下载的数据传输远远大于获取一个URL的数据量,所以按照上面的思路来做的话很可能造成大量的获取图片URL的operation占用资源造成图片下载的不及时,所以这里把获取图片URL的operation优先级降低,把图片下载operation的优先级提高,而图片下载又依赖于图片URL的获取,这样就基本符合原始SDWebImage的加载逻辑了。下面是逻辑处理代码。不对的地方欢迎指正交流。
SDWebImageFetchURLOperation.h
/* * This file is part of the SDWebImage package. * (c) Olivier Poitrey <rs@dailymotion.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import <Foundation/Foundation.h> #import "SDWebImageDownloader.h" #import "SDWebImageOperation.h" @interface SDWebImageFetchURLOperation : NSOperation <SDWebImageOperation> /** * The request used by the operation's connection. */ @property (strong, nonatomic) NSMutableURLRequest *request; /** * Whether the URL connection should consult the credential storage for authenticating the connection. `YES` by default. * * This is the value that is returned in the `NSURLConnectionDelegate` method `-connectionShouldUseCredentialStorage:`. */ @property (nonatomic, assign) BOOL shouldUseCredentialStorage; /** * The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`. * * This will be overridden by any shared credentials that exist for the username or password of the request URL, if present. */ @property (nonatomic, strong) NSURLCredential *credential; /** * Initializes a `SDWebImageFetchURLOperation` object * * @see SDWebImageDownloaderOperation * * @param request the URL request * @param completedBlock the block executed when the download is done. * @note the completed block is executed on the main queue for success. If errors are found, there is a chance the block will be executed on a background queue * @param cancelBlock the block executed if the download (operation) is cancelled * * @return the initialized instance */ - (id)initWithRequest:(NSMutableURLRequest *)request completed:(SDWebImageFetchURLCompletedBlock)completedBlock cancelled:(SDWebImageNoParamsBlock)cancelBlock; @end
SDWebImageFetchURLOperation.m
/* * This file is part of the SDWebImage package. * (c) Olivier Poitrey <rs@dailymotion.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageFetchURLOperation.h" #import "SDWebImageDecoder.h" #import "UIImage+MultiFormat.h" #import <ImageIO/ImageIO.h> #import "SDWebImageManager.h" @interface SDWebImageFetchURLOperation () <NSURLConnectionDataDelegate> @property (copy, nonatomic) SDWebImageFetchURLCompletedBlock completedBlock; @property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock; @property (assign, nonatomic, getter = isExecuting) BOOL executing; @property (assign, nonatomic, getter = isFinished) BOOL finished; @property (assign, nonatomic) NSInteger expectedSize; @property (strong, nonatomic) NSMutableData *urlData; @property (strong, nonatomic) NSURLConnection *connection; @property (strong, atomic) NSThread *thread; #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId; #endif @end @implementation SDWebImageFetchURLOperation { // size_t width, height; UIImageOrientation orientation; BOOL responseFromCached; } @synthesize executing = _executing; @synthesize finished = _finished; - (id)initWithRequest:(NSMutableURLRequest *)request completed:(SDWebImageFetchURLCompletedBlock)completedBlock cancelled:(SDWebImageNoParamsBlock)cancelBlock { if ((self = [super init])) { self.request = request; _shouldUseCredentialStorage = YES; _completedBlock = [completedBlock copy]; _cancelBlock = [cancelBlock copy]; _executing = NO; _finished = NO; _expectedSize = 0; responseFromCached = YES; // Initially wrong until `connection:willCacheResponse:` is called or not called } return self; } - (void)start { @synchronized (self) { if (self.isCancelled) { self.finished = YES; [self reset]; return; } self.executing = YES; self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; self.thread = [NSThread currentThread]; } [self.connection start]; if (self.connection) { [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self]; if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) { // Make sure to run the runloop in our background thread so it can process downloaded data // Note: we use a timeout to work around an issue with NSURLConnection cancel under iOS 5 // not waking up the runloop, leading to dead threads (see https://github.com/rs/SDWebImage/issues/466) CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false); } else { 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, [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)cancel { @synchronized (self) { if (self.thread) { [self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO]; } else { [self cancelInternal]; } } } - (void)cancelInternalAndStop { if (self.isFinished) return; [self cancelInternal]; CFRunLoopStop(CFRunLoopGetCurrent()); } - (void)cancelInternal { if (self.isFinished) return; [super cancel]; if (self.cancelBlock) self.cancelBlock(); if (self.connection) { [self.connection cancel]; [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self]; // As we cancelled the connection, its callback won't be called and thus won't // maintain the isFinished and isExecuting flags. if (self.isExecuting) self.executing = NO; if (!self.isFinished) self.finished = YES; } [self reset]; } - (void)done { self.finished = YES; self.executing = NO; [self reset]; } - (void)reset { self.cancelBlock = nil; self.completedBlock = nil; self.connection = nil; self.urlData = nil; self.thread = nil; } - (void)setFinished:(BOOL)finished { [self willChangeValueForKey:@"isFinished"]; _finished = finished; [self didChangeValueForKey:@"isFinished"]; } - (void)setExecuting:(BOOL)executing { [self willChangeValueForKey:@"isExecuting"]; _executing = executing; [self didChangeValueForKey:@"isExecuting"]; } - (BOOL)isConcurrent { return YES; } #pragma mark NSURLConnection (delegate) - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { if (![response respondsToSelector:@selector(statusCode)] || [((NSHTTPURLResponse *)response) statusCode] < 400) { NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0; self.expectedSize = expected; self.urlData = [[NSMutableData alloc] initWithCapacity:expected]; } else { [self.connection cancel]; [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:nil]; if (self.completedBlock) { self.completedBlock(nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES); } CFRunLoopStop(CFRunLoopGetCurrent()); [self done]; } } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [self.urlData appendData:data]; } - (void)connectionDidFinishLoading:(NSURLConnection *)aConnection { SDWebImageFetchURLCompletedBlock completionBlock = self.completedBlock; @synchronized(self) { CFRunLoopStop(CFRunLoopGetCurrent()); self.thread = nil; self.connection = nil; [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:nil]; } if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) { responseFromCached = NO; } if (completionBlock) { NSString *url = [[NSString alloc] initWithData:self.urlData encoding:NSUTF8StringEncoding]; if (!url) { completionBlock(nil, [NSError errorWithDomain:@"SDWebImageErrorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey : @"fetch image url failed"}], YES); } else { completionBlock(url, nil, YES); } } self.completionBlock = nil; [self done]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { CFRunLoopStop(CFRunLoopGetCurrent()); [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:nil]; if (self.completedBlock) { self.completedBlock(nil, error, YES); } [self done]; } - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { responseFromCached = NO; // If this method is called, it means the response wasn't read from cache if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) { // Prevents caching of responses return nil; } else { return cachedResponse; } } - (BOOL)shouldContinueWhenAppEntersBackground { return SDWebImageDownloaderContinueInBackground; } - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection { return self.shouldUseCredentialStorage; } - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{ if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; } else { if ([challenge previousFailureCount] == 0) { if (self.credential) { [[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge]; } else { [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } } else { [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } } } @end
其实这部分代码基本属于比葫芦画瓢,就是模仿SDWebImageDownloaderOperation来写的。写好了获取图片URL的Operation,就要处理逻辑方面的东西了。这部分的逻辑主要在SDWebImageDownloader中做修改,修改思路就是在每个下载图片的operation前面都添加一个获取图片的operation,然后让下载的operation依赖于获取图片URL的operation,但是相信大家使用SDWebImage的情形大部分还是在UITabView等列表中,而且图片下载的数据传输远远大于获取一个URL的数据量,所以按照上面的思路来做的话很可能造成大量的获取图片URL的operation占用资源造成图片下载的不及时,所以这里把获取图片URL的operation优先级降低,把图片下载operation的优先级提高,而图片下载又依赖于图片URL的获取,这样就基本符合原始SDWebImage的加载逻辑了。下面是逻辑处理代码。不对的地方欢迎指正交流。
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock { __block SDWebImageDownloaderOperation *operation; __block SDWebImageFetchURLOperation *fetchOperation; __weak SDWebImageDownloader *wself = self; [self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^{ NSTimeInterval timeoutInterval = wself.downloadTimeout; if (timeoutInterval == 0.0) { timeoutInterval = 15.0; } __block NSURL *mURL = [url copy]; if ([mURL.description hasPrefix:@"xxxxxxxxxxxxxxx"]) {//这里判断是不是需要进行二次加载的URL NSMutableURLRequest *prerequest = [[NSMutableURLRequest alloc] initWithURL:mURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:5.0]; [prerequest setHTTPMethod:@"GET"]; fetchOperation = [[SDWebImageFetchURLOperation alloc] initWithRequest:prerequest completed:^(NSString *url, NSError *error, BOOL finished) { if (!url) { return ; } if (operation) { [operation changeURL:[NSURL URLWithString:url]]; } } cancelled:^{ return ; }]; fetchOperation.queuePriority = NSOperationQueuePriorityLow;//降低获取URL线程优先级 } // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:mURL 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; } operation = [[SDWebImageDownloaderOperation alloc] initWithRequest:request options:options progress:^(NSInteger receivedSize, NSInteger expectedSize) { SDWebImageDownloader *sself = wself; if (!sself) return; NSArray *callbacksForURL = [sself callbacksForURL:url]; for (NSDictionary *callbacks in callbacksForURL) { SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey]; if (callback) callback(receivedSize, expectedSize); } } completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) { SDWebImageDownloader *sself = wself; if (!sself) return; NSArray *callbacksForURL = [sself callbacksForURL:url]; if (finished) { [sself removeCallbacksForURL:url]; } for (NSDictionary *callbacks in callbacksForURL) { SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey]; if (callback) callback(image, data, error, finished); } } cancelled:^{ SDWebImageDownloader *sself = wself; if (!sself) return; [sself removeCallbacksForURL:url]; }]; if (wself.username && wself.password) { operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession]; } // if (options & SDWebImageDownloaderHighPriority) { operation.queuePriority = NSOperationQueuePriorityHigh;//提高下载优先级 // } else if (options & SDWebImageDownloaderLowPriority) { // operation.queuePriority = NSOperationQueuePriorityLow; // } if (fetchOperation) {//如果有二次加载的operation存在,那么设置下载和获取operation的依赖关系并加入线程队列 [operation addDependency:fetchOperation]; [wself.downloadQueue addOperation:operation]; [wself.downloadQueue addOperation:fetchOperation]; }else{ [wself.downloadQueue addOperation:operation]; } if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) { // Emulate LIFO execution order by systematically adding new operations as last operation's dependency if (fetchOperation) { [wself.lastAddedOperation addDependency:operation]; wself.lastAddedOperation = fetchOperation; }else{ [wself.lastAddedOperation addDependency:operation]; wself.lastAddedOperation = operation; } } }]; return operation; }
相关文章推荐
- 群聊天
- 中国民族语文翻译局 (翻译软件)
- 渲染机制
- 从头搭建Openstack运行环境(六)--租户网络间路由与防火墙
- 一个整型数组里除了两个数字之外,其它数字都出现了两次。找出这两个数字
- PHP无限分类原理和几种实现方法
- ZOJ-3926-Parity Modulo P【人品题】
- Leetcode:9. Palindrome Number(JAVA)
- Java中数据存储在哪?以及equals和==的区别
- 前端开发规范文档v3.0.0
- Android全屏
- Centos 配置eth0 提示Device does not seem to be present
- CXF 的IP拦截
- 关于鼠标移动事件晃动问题解决方案
- linux下开启SSH,并且允许root用户远程登录,允许无密码登录
- python 系列之 - 多线程
- cancelsTouchesInView
- 一些简单的查询语法
- java.lang.IndexOutOfBondsException:setSpan(3..3) ends beyond length 2
- FZUoj 1408 位图【基础BFS】