2009-09-21 09:38:30 +08:00
/ *
2009-09-22 01:34:32 +08:00
* This file is part of the SDWebImage package .
* ( c ) Olivier Poitrey < rs @ dailymotion . com >
2009-09-21 09:38:30 +08:00
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
* /
2009-09-22 01:34:32 +08:00
# import "SDWebImageDownloader.h"
2018-03-31 05:34:10 +08:00
# import "SDWebImageDownloaderConfig.h"
2012-11-04 16:50:23 +08:00
# import "SDWebImageDownloaderOperation.h"
2018-04-18 12:50:05 +08:00
# import "SDWebImageError.h"
2019-05-13 15:08:58 +08:00
# import "SDInternalMacros.h"
2012-05-09 22:29:13 +08:00
2019-03-31 23:07:20 +08:00
NSNotificationName const SDWebImageDownloadStartNotification = @ "SDWebImageDownloadStartNotification" ;
NSNotificationName const SDWebImageDownloadReceiveResponseNotification = @ "SDWebImageDownloadReceiveResponseNotification" ;
NSNotificationName const SDWebImageDownloadStopNotification = @ "SDWebImageDownloadStopNotification" ;
NSNotificationName const SDWebImageDownloadFinishNotification = @ "SDWebImageDownloadFinishNotification" ;
2018-03-31 15:51:46 +08:00
static void * SDWebImageDownloaderContext = & SDWebImageDownloaderContext ;
2018-01-18 15:08:12 +08:00
@ interface SDWebImageDownloadToken ( )
2018-03-31 15:02:10 +08:00
@ property ( nonatomic , strong , nullable , readwrite ) NSURL * url ;
2018-07-09 16:32:47 +08:00
@ property ( nonatomic , strong , nullable , readwrite ) NSURLRequest * request ;
@ property ( nonatomic , strong , nullable , readwrite ) NSURLResponse * response ;
2018-03-31 15:02:10 +08:00
@ property ( nonatomic , strong , nullable , readwrite ) id downloadOperationCancelToken ;
2018-03-31 05:34:10 +08:00
@ property ( nonatomic , weak , nullable ) NSOperation < SDWebImageDownloaderOperation > * downloadOperation ;
2018-03-31 15:02:10 +08:00
@ property ( nonatomic , weak , nullable ) SDWebImageDownloader * downloader ;
2018-04-05 07:26:50 +08:00
@ property ( nonatomic , assign , getter = isCancelled ) BOOL cancelled ;
2018-01-18 15:08:12 +08:00
2018-08-09 17:21:24 +08:00
- ( nonnull instancetype ) init NS_UNAVAILABLE ;
+ ( nonnull instancetype ) new NS_UNAVAILABLE ;
2018-08-09 20:43:35 +08:00
- ( nonnull instancetype ) initWithDownloadOperation : ( nullable NSOperation < SDWebImageDownloaderOperation > * ) downloadOperation ;
2018-08-09 17:21:24 +08:00
2014-09-01 18:11:27 +08:00
@ end
2016-06-07 00:12:56 +08:00
@ interface SDWebImageDownloader ( ) < NSURLSessionTaskDelegate , NSURLSessionDataDelegate >
2009-09-21 09:38:30 +08:00
2016-06-03 22:25:21 +08:00
@ property ( strong , nonatomic , nonnull ) NSOperationQueue * downloadQueue ;
@ property ( weak , nonatomic , nullable ) NSOperation * lastAddedOperation ;
2018-04-05 07:26:50 +08:00
@ property ( strong , nonatomic , nonnull ) NSMutableDictionary < NSURL * , NSOperation < SDWebImageDownloaderOperation > * > * URLOperations ;
2018-10-27 18:44:40 +08:00
@ property ( strong , nonatomic , nullable ) NSMutableDictionary < NSString * , NSString * > * HTTPHeaders ;
@ property ( strong , nonatomic , nonnull ) dispatch_semaphore _t HTTPHeadersLock ; // A lock to keep the access to ` HTTPHeaders` thread - safe
@ property ( strong , nonatomic , nonnull ) dispatch_semaphore _t operationsLock ; // A lock to keep the access to ` URLOperations` thread - safe
2009-09-21 09:38:30 +08:00
2016-06-07 00:12:56 +08:00
// The session in which data tasks will run
@ property ( strong , nonatomic ) NSURLSession * session ;
2012-11-04 16:50:23 +08:00
@ end
2010-10-06 23:37:57 +08:00
2012-11-04 16:50:23 +08:00
@ implementation SDWebImageDownloader
2011-05-06 16:30:50 +08:00
2014-01-07 09:12:24 +08:00
+ ( void ) initialize {
2010-10-03 16:04:41 +08:00
// Bind SDNetworkActivityIndicator if available ( download it here : http : // github . com / rs / SDNetworkActivityIndicator )
// To use it , just add # import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import
2014-01-07 09:12:24 +08:00
if ( NSClassFromString ( @ "SDNetworkActivityIndicator" ) ) {
2012-11-04 16:50:23 +08:00
2012-04-25 09:56:56 +08:00
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Warc-performSelector-leaks"
2010-10-03 16:04:41 +08:00
id activityIndicator = [ NSClassFromString ( @ "SDNetworkActivityIndicator" ) performSelector : NSSelectorFromString ( @ "sharedActivityIndicator" ) ] ;
2012-04-25 09:56:56 +08:00
# pragma clang diagnostic pop
2012-03-29 03:27:31 +08:00
// Remove observer in case it was previously added .
[ [ NSNotificationCenter defaultCenter ] removeObserver : activityIndicator name : SDWebImageDownloadStartNotification object : nil ] ;
[ [ NSNotificationCenter defaultCenter ] removeObserver : activityIndicator name : SDWebImageDownloadStopNotification object : nil ] ;
2010-10-03 16:04:41 +08:00
[ [ NSNotificationCenter defaultCenter ] addObserver : activityIndicator
selector : NSSelectorFromString ( @ "startActivity" )
name : SDWebImageDownloadStartNotification object : nil ] ;
[ [ NSNotificationCenter defaultCenter ] addObserver : activityIndicator
selector : NSSelectorFromString ( @ "stopActivity" )
name : SDWebImageDownloadStopNotification object : nil ] ;
}
2009-09-21 09:38:30 +08:00
}
2016-10-01 14:28:21 +08:00
+ ( nonnull instancetype ) sharedDownloader {
2012-11-04 16:50:23 +08:00
static dispatch_once _t once ;
static id instance ;
2014-01-07 09:12:24 +08:00
dispatch_once ( & once , ^ {
2014-01-07 09:30:28 +08:00
instance = [ self new ] ;
2014-01-07 09:12:24 +08:00
} ) ;
2012-11-04 16:50:23 +08:00
return instance ;
2010-06-09 10:09:18 +08:00
}
2016-06-03 22:25:21 +08:00
- ( nonnull instancetype ) init {
2018-03-31 05:34:10 +08:00
return [ self initWithConfig : SDWebImageDownloaderConfig . defaultDownloaderConfig ] ;
2016-08-25 05:06:12 +08:00
}
2018-03-31 05:34:10 +08:00
- ( instancetype ) initWithConfig : ( SDWebImageDownloaderConfig * ) config {
self = [ super init ] ;
if ( self ) {
if ( ! config ) {
config = SDWebImageDownloaderConfig . defaultDownloaderConfig ;
}
_config = [ config copy ] ;
2018-03-31 15:51:46 +08:00
[ _config addObserver : self forKeyPath : NSStringFromSelector ( @ selector ( maxConcurrentDownloads ) ) options : 0 context : SDWebImageDownloaderContext ] ;
2014-01-07 09:30:28 +08:00
_downloadQueue = [ NSOperationQueue new ] ;
2018-03-31 15:51:46 +08:00
_downloadQueue . maxConcurrentOperationCount = _config . maxConcurrentDownloads ;
2016-09-01 20:05:30 +08:00
_downloadQueue . name = @ "com.hackemist.SDWebImageDownloader" ;
2014-09-01 18:19:21 +08:00
_URLOperations = [ NSMutableDictionary new ] ;
2018-08-10 15:20:55 +08:00
NSMutableDictionary < NSString * , NSString * > * headerDictionary = [ NSMutableDictionary dictionary ] ;
2018-07-31 16:29:39 +08:00
NSString * userAgent = nil ;
2018-08-06 14:38:35 +08:00
# if SD_UIKIT
2018-07-31 16:29:39 +08:00
// User - Agent Header ; see http : // www . w3 . org / Protocols / rfc2616 / rfc2616 - sec14 . html # sec14 .43
userAgent = [ NSString stringWithFormat : @ "%@/%@ (%@; iOS %@; Scale/%0.2f)" , [ [ NSBundle mainBundle ] infoDictionary ] [ ( __bridge NSString * ) kCFBundleExecutableKey ] ? : [ [ NSBundle mainBundle ] infoDictionary ] [ ( __bridge NSString * ) kCFBundleIdentifierKey ] , [ [ NSBundle mainBundle ] infoDictionary ] [ @ "CFBundleShortVersionString" ] ? : [ [ NSBundle mainBundle ] infoDictionary ] [ ( __bridge NSString * ) kCFBundleVersionKey ] , [ [ UIDevice currentDevice ] model ] , [ [ UIDevice currentDevice ] systemVersion ] , [ [ UIScreen mainScreen ] scale ] ] ;
# elif SD_WATCH
// User - Agent Header ; see http : // www . w3 . org / Protocols / rfc2616 / rfc2616 - sec14 . html # sec14 .43
userAgent = [ NSString stringWithFormat : @ "%@/%@ (%@; watchOS %@; Scale/%0.2f)" , [ [ NSBundle mainBundle ] infoDictionary ] [ ( __bridge NSString * ) kCFBundleExecutableKey ] ? : [ [ NSBundle mainBundle ] infoDictionary ] [ ( __bridge NSString * ) kCFBundleIdentifierKey ] , [ [ NSBundle mainBundle ] infoDictionary ] [ @ "CFBundleShortVersionString" ] ? : [ [ NSBundle mainBundle ] infoDictionary ] [ ( __bridge NSString * ) kCFBundleVersionKey ] , [ [ WKInterfaceDevice currentDevice ] model ] , [ [ WKInterfaceDevice currentDevice ] systemVersion ] , [ [ WKInterfaceDevice currentDevice ] screenScale ] ] ;
# elif SD_MAC
userAgent = [ NSString stringWithFormat : @ "%@/%@ (Mac OS X %@)" , [ [ NSBundle mainBundle ] infoDictionary ] [ ( __bridge NSString * ) kCFBundleExecutableKey ] ? : [ [ NSBundle mainBundle ] infoDictionary ] [ ( __bridge NSString * ) kCFBundleIdentifierKey ] , [ [ NSBundle mainBundle ] infoDictionary ] [ @ "CFBundleShortVersionString" ] ? : [ [ NSBundle mainBundle ] infoDictionary ] [ ( __bridge NSString * ) kCFBundleVersionKey ] , [ [ NSProcessInfo processInfo ] operatingSystemVersionString ] ] ;
# endif
if ( userAgent ) {
if ( ! [ userAgent canBeConvertedToEncoding : NSASCIIStringEncoding ] ) {
NSMutableString * mutableUserAgent = [ userAgent mutableCopy ] ;
if ( CFStringTransform ( ( __bridge CFMutableStringRef ) ( mutableUserAgent ) , NULL , ( __bridge CFStringRef ) @ "Any-Latin; Latin-ASCII; [:^ASCII:] Remove" , false ) ) {
userAgent = mutableUserAgent ;
}
}
headerDictionary [ @ "User-Agent" ] = userAgent ;
}
headerDictionary [ @ "Accept" ] = @ "image/*;q=0.8" ;
2018-10-27 18:44:40 +08:00
_HTTPHeaders = headerDictionary ;
_HTTPHeadersLock = dispatch_semaphore _create ( 1 ) ;
2018-01-26 19:15:26 +08:00
_operationsLock = dispatch_semaphore _create ( 1 ) ;
2018-03-31 05:34:10 +08:00
NSURLSessionConfiguration * sessionConfiguration = _config . sessionConfiguration ;
if ( ! sessionConfiguration ) {
sessionConfiguration = [ NSURLSessionConfiguration defaultSessionConfiguration ] ;
}
/ * *
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls .
* /
_session = [ NSURLSession sessionWithConfiguration : sessionConfiguration
2017-05-04 03:29:01 +08:00
delegate : self
delegateQueue : nil ] ;
2018-03-31 05:34:10 +08:00
}
return self ;
2017-05-04 03:29:01 +08:00
}
2018-03-31 15:51:46 +08:00
- ( void ) dealloc {
[ self . session invalidateAndCancel ] ;
self . session = nil ;
[ self . downloadQueue cancelAllOperations ] ;
[ self . config removeObserver : self forKeyPath : NSStringFromSelector ( @ selector ( maxConcurrentDownloads ) ) context : SDWebImageDownloaderContext ] ;
}
2017-11-27 00:08:50 +08:00
- ( void ) invalidateSessionAndCancel : ( BOOL ) cancelPendingOperations {
2018-01-04 20:11:20 +08:00
if ( self = = [ SDWebImageDownloader sharedDownloader ] ) {
return ;
}
2017-11-27 00:08:50 +08:00
if ( cancelPendingOperations ) {
[ self . session invalidateAndCancel ] ;
} else {
[ self . session finishTasksAndInvalidate ] ;
}
}
2016-06-03 22:25:21 +08:00
- ( void ) setValue : ( nullable NSString * ) value forHTTPHeaderField : ( nullable NSString * ) field {
2018-07-03 21:18:02 +08:00
if ( ! field ) {
return ;
}
2018-10-27 18:44:40 +08:00
SD_LOCK ( self . HTTPHeadersLock ) ;
[ self . HTTPHeaders setValue : value forKey : field ] ;
SD_UNLOCK ( self . HTTPHeadersLock ) ;
2013-02-17 06:33:41 +08:00
}
2016-06-03 22:25:21 +08:00
- ( nullable NSString * ) valueForHTTPHeaderField : ( nullable NSString * ) field {
2018-01-31 03:22:10 +08:00
if ( ! field ) {
return nil ;
}
2018-10-27 18:44:40 +08:00
SD_LOCK ( self . HTTPHeadersLock ) ;
NSString * value = [ self . HTTPHeaders objectForKey : field ] ;
SD_UNLOCK ( self . HTTPHeadersLock ) ;
return value ;
2013-02-17 06:33:41 +08:00
}
2019-03-31 16:34:23 +08:00
- ( nullable SDWebImageDownloadToken * ) downloadImageWithURL : ( NSURL * ) url
completed : ( SDWebImageDownloaderCompletedBlock ) completedBlock {
return [ self downloadImageWithURL : url options : 0 progress : nil completed : completedBlock ] ;
2012-03-20 02:12:14 +08:00
}
2019-03-31 16:34:23 +08:00
- ( nullable SDWebImageDownloadToken * ) downloadImageWithURL : ( NSURL * ) url
options : ( SDWebImageDownloaderOptions ) options
progress : ( SDWebImageDownloaderProgressBlock ) progressBlock
completed : ( SDWebImageDownloaderCompletedBlock ) completedBlock {
2018-04-02 03:24:23 +08:00
return [ self downloadImageWithURL : url options : options context : nil progress : progressBlock completed : completedBlock ] ;
2018-01-23 20:45:31 +08:00
}
2016-06-03 22:25:21 +08:00
- ( nullable SDWebImageDownloadToken * ) downloadImageWithURL : ( nullable NSURL * ) url
options : ( SDWebImageDownloaderOptions ) options
2018-04-02 03:24:23 +08:00
context : ( nullable SDWebImageContext * ) context
2016-06-03 22:25:21 +08:00
progress : ( nullable SDWebImageDownloaderProgressBlock ) progressBlock
2018-04-02 03:24:23 +08:00
completed : ( nullable SDWebImageDownloaderCompletedBlock ) completedBlock {
2013-01-17 11:22:33 +08:00
// The URL will be used as the key to the callbacks dictionary so it cannot be nil . If it is nil immediately call the completed block with no image or data .
2014-01-07 09:12:24 +08:00
if ( url = = nil ) {
2018-03-24 00:15:27 +08:00
if ( completedBlock ) {
2018-04-18 12:50:05 +08:00
NSError * error = [ NSError errorWithDomain : SDWebImageErrorDomain code : SDWebImageErrorInvalidURL userInfo : @ { NSLocalizedDescriptionKey : @ "Image url is nil" } ] ;
2018-03-24 00:15:27 +08:00
completedBlock ( nil , nil , error , YES ) ;
2013-01-17 03:53:38 +08:00
}
2014-09-01 18:11:27 +08:00
return nil ;
2013-01-17 03:53:38 +08:00
}
2018-01-20 14:44:00 +08:00
2018-09-17 17:11:15 +08:00
SD_LOCK ( self . operationsLock ) ;
2018-04-05 07:26:50 +08:00
NSOperation < SDWebImageDownloaderOperation > * operation = [ self . URLOperations objectForKey : url ] ;
2019-01-26 17:02:27 +08:00
// There is a case that the operation may be marked as finished or cancelled , but not been removed from ` self . URLOperations` .
if ( ! operation || operation . isFinished || operation . isCancelled ) {
2018-08-02 18:37:41 +08:00
operation = [ self createDownloaderOperationWithUrl : url options : options context : context ] ;
2018-03-24 00:15:27 +08:00
if ( ! operation ) {
2018-09-17 17:11:15 +08:00
SD_UNLOCK ( self . operationsLock ) ;
2018-03-24 00:15:27 +08:00
if ( completedBlock ) {
2018-04-18 12:50:05 +08:00
NSError * error = [ NSError errorWithDomain : SDWebImageErrorDomain code : SDWebImageErrorInvalidDownloadOperation userInfo : @ { NSLocalizedDescriptionKey : @ "Downloader operation is nil" } ] ;
2018-03-24 00:15:27 +08:00
completedBlock ( nil , nil , error , YES ) ;
}
return nil ;
}
2019-03-18 19:35:53 +08:00
@ weakify ( self ) ;
2018-01-20 14:44:00 +08:00
operation . completionBlock = ^ {
2019-03-18 19:35:53 +08:00
@ strongify ( self ) ;
if ( ! self ) {
2018-01-26 19:15:26 +08:00
return ;
}
2019-03-18 19:35:53 +08:00
SD_LOCK ( self . operationsLock ) ;
[ self . URLOperations removeObjectForKey : url ] ;
SD_UNLOCK ( self . operationsLock ) ;
2018-01-20 14:44:00 +08:00
} ;
2018-07-24 13:05:12 +08:00
self . URLOperations [ url ] = operation ;
2018-02-28 01:09:26 +08:00
// Add operation to operation queue only after all configuration done according to Apple ' s doc .
// ` addOperation : ` does not synchronously execute the ` operation . completionBlock` so this will not cause deadlock .
[ self . downloadQueue addOperation : operation ] ;
2018-01-20 14:44:00 +08:00
}
2019-01-26 17:02:27 +08:00
else if ( ! operation . isExecuting ) {
if ( options & SDWebImageDownloaderHighPriority ) {
operation . queuePriority = NSOperationQueuePriorityHigh ;
} else if ( options & SDWebImageDownloaderLowPriority ) {
operation . queuePriority = NSOperationQueuePriorityLow ;
} else {
operation . queuePriority = NSOperationQueuePriorityNormal ;
}
}
2018-09-17 17:11:15 +08:00
SD_UNLOCK ( self . operationsLock ) ;
2018-08-02 18:37:41 +08:00
2018-01-20 14:44:00 +08:00
id downloadOperationCancelToken = [ operation addHandlersForProgress : progressBlock completed : completedBlock ] ;
2018-08-09 14:59:04 +08:00
SDWebImageDownloadToken * token = [ [ SDWebImageDownloadToken alloc ] initWithDownloadOperation : operation ] ;
2018-01-20 14:44:00 +08:00
token . url = url ;
2018-04-05 16:22:58 +08:00
token . request = operation . request ;
2018-01-20 14:44:00 +08:00
token . downloadOperationCancelToken = downloadOperationCancelToken ;
2018-03-31 15:02:10 +08:00
token . downloader = self ;
2018-08-02 18:37:41 +08:00
2014-09-01 18:11:27 +08:00
return token ;
2012-11-06 10:31:03 +08:00
}
2012-05-09 22:29:13 +08:00
2018-08-02 18:37:41 +08:00
- ( nullable NSOperation < SDWebImageDownloaderOperation > * ) createDownloaderOperationWithUrl : ( nonnull NSURL * ) url
options : ( SDWebImageDownloaderOptions ) options
context : ( nullable SDWebImageContext * ) context {
NSTimeInterval timeoutInterval = self . config . downloadTimeout ;
2018-05-24 10:37:19 +08:00
if ( timeoutInterval = = 0.0 ) {
timeoutInterval = 15.0 ;
2018-01-20 14:44:00 +08:00
}
2018-08-02 18:37:41 +08:00
2018-05-24 10:37:19 +08:00
// In order to prevent from potential duplicate caching ( NSURLCache + SDImageCache ) we disable the cache for image requests if told otherwise
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData ;
2018-08-02 18:37:41 +08:00
NSMutableURLRequest * mutableRequest = [ [ NSMutableURLRequest alloc ] initWithURL : url cachePolicy : cachePolicy timeoutInterval : timeoutInterval ] ;
mutableRequest . HTTPShouldHandleCookies = ( options & SDWebImageDownloaderHandleCookies ) ;
mutableRequest . HTTPShouldUsePipelining = YES ;
2018-10-27 18:44:40 +08:00
SD_LOCK ( self . HTTPHeadersLock ) ;
2018-08-02 18:37:41 +08:00
mutableRequest . allHTTPHeaderFields = self . HTTPHeaders ;
2018-10-27 18:44:40 +08:00
SD_UNLOCK ( self . HTTPHeadersLock ) ;
2018-08-02 18:37:41 +08:00
id < SDWebImageDownloaderRequestModifier > requestModifier ;
if ( [ context valueForKey : SDWebImageContextDownloadRequestModifier ] ) {
requestModifier = [ context valueForKey : SDWebImageContextDownloadRequestModifier ] ;
} else {
requestModifier = self . requestModifier ;
}
2018-05-24 10:37:19 +08:00
2018-08-02 18:37:41 +08:00
NSURLRequest * request ;
if ( requestModifier ) {
NSURLRequest * modifiedRequest = [ requestModifier modifiedRequestWithRequest : [ mutableRequest copy ] ] ;
// If modified request is nil , early return
if ( ! modifiedRequest ) {
return nil ;
} else {
request = [ modifiedRequest copy ] ;
}
} else {
request = [ mutableRequest copy ] ;
2018-01-20 14:44:00 +08:00
}
2018-08-02 18:37:41 +08:00
Class operationClass = self . config . operationClass ;
if ( operationClass && [ operationClass isSubclassOfClass : [ NSOperation class ] ] && [ operationClass conformsToProtocol : @ protocol ( SDWebImageDownloaderOperation ) ] ) {
// Custom operation class
} else {
operationClass = [ SDWebImageDownloaderOperation class ] ;
2018-05-24 10:37:19 +08:00
}
2018-08-02 18:37:41 +08:00
NSOperation < SDWebImageDownloaderOperation > * operation = [ [ operationClass alloc ] initWithRequest : request inSession : self . session options : options context : context ] ;
2018-05-24 10:37:19 +08:00
2018-08-05 00:11:04 +08:00
if ( [ operation respondsToSelector : @ selector ( setCredential : ) ] ) {
if ( self . config . urlCredential ) {
operation . credential = self . config . urlCredential ;
} else if ( self . config . username && self . config . password ) {
operation . credential = [ NSURLCredential credentialWithUser : self . config . username password : self . config . password persistence : NSURLCredentialPersistenceForSession ] ;
}
}
if ( [ operation respondsToSelector : @ selector ( setMinimumProgressInterval : ) ] ) {
2019-03-14 15:27:29 +08:00
operation . minimumProgressInterval = MIN ( MAX ( self . config . minimumProgressInterval , 0 ) , 1 ) ;
2018-05-24 10:37:19 +08:00
}
if ( options & SDWebImageDownloaderHighPriority ) {
operation . queuePriority = NSOperationQueuePriorityHigh ;
} else if ( options & SDWebImageDownloaderLowPriority ) {
operation . queuePriority = NSOperationQueuePriorityLow ;
}
2018-08-02 18:37:41 +08:00
if ( self . config . executionOrder = = SDWebImageDownloaderLIFOExecutionOrder ) {
2018-05-24 10:37:19 +08:00
// Emulate LIFO execution order by systematically adding new operations as last operation ' s dependency
[ self . lastAddedOperation addDependency : operation ] ;
self . lastAddedOperation = operation ;
}
2018-08-02 18:37:41 +08:00
2018-05-24 10:37:19 +08:00
return operation ;
2014-09-01 18:11:27 +08:00
}
2012-05-09 22:29:13 +08:00
2016-06-03 22:25:21 +08:00
- ( void ) cancel : ( nullable SDWebImageDownloadToken * ) token {
2018-01-20 14:44:00 +08:00
NSURL * url = token . url ;
if ( ! url ) {
return ;
}
2018-09-17 17:11:15 +08:00
SD_LOCK ( self . operationsLock ) ;
2018-08-02 18:37:41 +08:00
NSOperation < SDWebImageDownloaderOperation > * operation = [ self . URLOperations objectForKey : url ] ;
2018-01-26 19:15:26 +08:00
if ( operation ) {
BOOL canceled = [ operation cancel : token . downloadOperationCancelToken ] ;
if ( canceled ) {
[ self . URLOperations removeObjectForKey : url ] ;
}
2018-01-20 14:44:00 +08:00
}
2018-09-17 17:11:15 +08:00
SD_UNLOCK ( self . operationsLock ) ;
2012-11-06 10:31:03 +08:00
}
2018-03-31 15:51:46 +08:00
- ( void ) cancelAllDownloads {
[ self . downloadQueue cancelAllOperations ] ;
}
# pragma mark - Properties
2018-03-10 00:33:28 +08:00
- ( BOOL ) isSuspended {
return self . downloadQueue . isSuspended ;
}
2014-06-02 17:12:26 +08:00
- ( void ) setSuspended : ( BOOL ) suspended {
2017-07-24 20:46:31 +08:00
self . downloadQueue . suspended = suspended ;
2014-06-02 17:12:26 +08:00
}
2018-03-31 05:34:10 +08:00
- ( NSUInteger ) currentDownloadCount {
return self . downloadQueue . operationCount ;
}
2018-03-31 15:51:46 +08:00
- ( NSURLSessionConfiguration * ) sessionConfiguration {
return self . session . configuration ;
}
# pragma mark - KVO
- ( void ) observeValueForKeyPath : ( NSString * ) keyPath ofObject : ( id ) object change : ( NSDictionary < NSKeyValueChangeKey , id > * ) change context : ( void * ) context {
if ( context = = SDWebImageDownloaderContext ) {
if ( [ keyPath isEqualToString : NSStringFromSelector ( @ selector ( maxConcurrentDownloads ) ) ] ) {
self . downloadQueue . maxConcurrentOperationCount = self . config . maxConcurrentDownloads ;
}
} else {
[ super observeValueForKeyPath : keyPath ofObject : object change : change context : context ] ;
}
2016-03-21 13:16:05 +08:00
}
2016-06-07 00:12:56 +08:00
# pragma mark Helper methods
2018-04-05 07:26:50 +08:00
- ( NSOperation < SDWebImageDownloaderOperation > * ) operationWithTask : ( NSURLSessionTask * ) task {
NSOperation < SDWebImageDownloaderOperation > * returnOperation = nil ;
for ( NSOperation < SDWebImageDownloaderOperation > * operation in self . downloadQueue . operations ) {
if ( [ operation respondsToSelector : @ selector ( dataTask ) ] ) {
if ( operation . dataTask . taskIdentifier = = task . taskIdentifier ) {
returnOperation = operation ;
break ;
}
2016-06-07 00:12:56 +08:00
}
}
return returnOperation ;
}
# pragma mark NSURLSessionDataDelegate
- ( void ) URLSession : ( NSURLSession * ) session
dataTask : ( NSURLSessionDataTask * ) dataTask
didReceiveResponse : ( NSURLResponse * ) response
completionHandler : ( void ( ^ ) ( NSURLSessionResponseDisposition disposition ) ) completionHandler {
// Identify the operation that runs this task and pass it the delegate method
2018-04-05 07:26:50 +08:00
NSOperation < SDWebImageDownloaderOperation > * dataOperation = [ self operationWithTask : dataTask ] ;
2018-01-28 23:44:53 +08:00
if ( [ dataOperation respondsToSelector : @ selector ( URLSession : dataTask : didReceiveResponse : completionHandler : ) ] ) {
[ dataOperation URLSession : session dataTask : dataTask didReceiveResponse : response completionHandler : completionHandler ] ;
} else {
if ( completionHandler ) {
completionHandler ( NSURLSessionResponseAllow ) ;
}
}
2016-06-07 00:12:56 +08:00
}
- ( void ) URLSession : ( NSURLSession * ) session dataTask : ( NSURLSessionDataTask * ) dataTask didReceiveData : ( NSData * ) data {
// Identify the operation that runs this task and pass it the delegate method
2018-04-05 07:26:50 +08:00
NSOperation < SDWebImageDownloaderOperation > * dataOperation = [ self operationWithTask : dataTask ] ;
2018-01-28 23:44:53 +08:00
if ( [ dataOperation respondsToSelector : @ selector ( URLSession : dataTask : didReceiveData : ) ] ) {
[ dataOperation URLSession : session dataTask : dataTask didReceiveData : data ] ;
}
2016-06-07 00:12:56 +08:00
}
- ( void ) URLSession : ( NSURLSession * ) session
dataTask : ( NSURLSessionDataTask * ) dataTask
willCacheResponse : ( NSCachedURLResponse * ) proposedResponse
completionHandler : ( void ( ^ ) ( NSCachedURLResponse * cachedResponse ) ) completionHandler {
// Identify the operation that runs this task and pass it the delegate method
2018-04-05 07:26:50 +08:00
NSOperation < SDWebImageDownloaderOperation > * dataOperation = [ self operationWithTask : dataTask ] ;
2018-01-28 23:44:53 +08:00
if ( [ dataOperation respondsToSelector : @ selector ( URLSession : dataTask : willCacheResponse : completionHandler : ) ] ) {
[ dataOperation URLSession : session dataTask : dataTask willCacheResponse : proposedResponse completionHandler : completionHandler ] ;
} else {
if ( completionHandler ) {
completionHandler ( proposedResponse ) ;
}
}
2016-06-07 00:12:56 +08:00
}
# pragma mark NSURLSessionTaskDelegate
- ( void ) URLSession : ( NSURLSession * ) session task : ( NSURLSessionTask * ) task didCompleteWithError : ( NSError * ) error {
2017-12-13 20:58:20 +08:00
2016-06-07 00:12:56 +08:00
// Identify the operation that runs this task and pass it the delegate method
2018-04-05 07:26:50 +08:00
NSOperation < SDWebImageDownloaderOperation > * dataOperation = [ self operationWithTask : task ] ;
2018-01-28 23:44:53 +08:00
if ( [ dataOperation respondsToSelector : @ selector ( URLSession : task : didCompleteWithError : ) ] ) {
[ dataOperation URLSession : session task : task didCompleteWithError : error ] ;
}
2016-06-07 00:12:56 +08:00
}
2016-08-31 00:56:15 +08:00
- ( void ) URLSession : ( NSURLSession * ) session task : ( NSURLSessionTask * ) task willPerformHTTPRedirection : ( NSHTTPURLResponse * ) response newRequest : ( NSURLRequest * ) request completionHandler : ( void ( ^ ) ( NSURLRequest * _Nullable ) ) completionHandler {
2017-12-13 20:58:20 +08:00
// Identify the operation that runs this task and pass it the delegate method
2018-04-05 07:26:50 +08:00
NSOperation < SDWebImageDownloaderOperation > * dataOperation = [ self operationWithTask : task ] ;
2017-12-10 03:27:08 +08:00
if ( [ dataOperation respondsToSelector : @ selector ( URLSession : task : willPerformHTTPRedirection : newRequest : completionHandler : ) ] ) {
[ dataOperation URLSession : session task : task willPerformHTTPRedirection : response newRequest : request completionHandler : completionHandler ] ;
} else {
2018-01-28 23:44:53 +08:00
if ( completionHandler ) {
completionHandler ( request ) ;
}
2017-12-10 03:27:08 +08:00
}
2016-08-31 00:56:15 +08:00
}
2016-06-07 00:12:56 +08:00
- ( void ) URLSession : ( NSURLSession * ) session task : ( NSURLSessionTask * ) task didReceiveChallenge : ( NSURLAuthenticationChallenge * ) challenge completionHandler : ( void ( ^ ) ( NSURLSessionAuthChallengeDisposition disposition , NSURLCredential * credential ) ) completionHandler {
// Identify the operation that runs this task and pass it the delegate method
2018-04-05 07:26:50 +08:00
NSOperation < SDWebImageDownloaderOperation > * dataOperation = [ self operationWithTask : task ] ;
2018-01-28 23:44:53 +08:00
if ( [ dataOperation respondsToSelector : @ selector ( URLSession : task : didReceiveChallenge : completionHandler : ) ] ) {
[ dataOperation URLSession : session task : task didReceiveChallenge : challenge completionHandler : completionHandler ] ;
} else {
if ( completionHandler ) {
completionHandler ( NSURLSessionAuthChallengePerformDefaultHandling , nil ) ;
}
}
2016-06-07 00:12:56 +08:00
}
2009-09-21 09:38:30 +08:00
@ end
2018-03-31 15:02:10 +08:00
@ implementation SDWebImageDownloadToken
2018-03-24 00:15:27 +08:00
- ( void ) dealloc {
2018-08-09 14:59:04 +08:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self name : SDWebImageDownloadReceiveResponseNotification object : nil ] ;
2018-03-24 00:15:27 +08:00
}
2018-08-09 14:59:04 +08:00
- ( instancetype ) initWithDownloadOperation : ( NSOperation < SDWebImageDownloaderOperation > * ) downloadOperation {
self = [ super init ] ;
if ( self ) {
2018-08-08 15:36:57 +08:00
_downloadOperation = downloadOperation ;
2018-08-09 14:59:04 +08:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self selector : @ selector ( downloadReceiveResponse : ) name : SDWebImageDownloadReceiveResponseNotification object : downloadOperation ] ;
2018-03-24 00:15:27 +08:00
}
2018-08-09 14:59:04 +08:00
return self ;
2018-03-24 00:15:27 +08:00
}
- ( void ) downloadReceiveResponse : ( NSNotification * ) notification {
NSOperation < SDWebImageDownloaderOperation > * downloadOperation = notification . object ;
2018-08-09 14:59:04 +08:00
if ( downloadOperation && downloadOperation = = self . downloadOperation ) {
2018-03-24 00:15:27 +08:00
self . response = downloadOperation . response ;
}
}
2018-03-31 15:02:10 +08:00
- ( void ) cancel {
@ synchronized ( self ) {
2018-04-05 07:26:50 +08:00
if ( self . isCancelled ) {
2018-03-31 15:02:10 +08:00
return ;
}
2018-04-05 07:26:50 +08:00
self . cancelled = YES ;
2018-03-31 15:02:10 +08:00
if ( self . downloader ) {
// Downloader is alive , cancel token
[ self . downloader cancel : self ] ;
} else {
// Downloader is dealloced , only cancel download operation
[ self . downloadOperation cancel : self . downloadOperationCancelToken ] ;
}
self . downloadOperationCancelToken = nil ;
}
}
@ end
2018-04-15 16:40:42 +08:00
2018-05-17 12:00:31 +08:00
@ implementation SDWebImageDownloader ( SDImageLoader )
2018-04-15 16:40:42 +08:00
2019-03-25 18:44:41 +08:00
- ( BOOL ) canRequestImageForURL : ( NSURL * ) url {
2018-04-15 16:40:42 +08:00
if ( ! url ) {
return NO ;
}
2018-04-25 14:21:38 +08:00
// Always pass YES to let URLSession or custom download operation to determine
2018-04-15 16:40:42 +08:00
return YES ;
}
2019-03-25 18:44:41 +08:00
- ( id < SDWebImageOperation > ) requestImageWithURL : ( NSURL * ) url options : ( SDWebImageOptions ) options context : ( SDWebImageContext * ) context progress : ( SDImageLoaderProgressBlock ) progressBlock completed : ( SDImageLoaderCompletedBlock ) completedBlock {
2018-07-21 23:44:06 +08:00
UIImage * cachedImage = context [ SDWebImageContextLoaderCachedImage ] ;
2018-04-15 16:40:42 +08:00
SDWebImageDownloaderOptions downloaderOptions = 0 ;
if ( options & SDWebImageLowPriority ) downloaderOptions | = SDWebImageDownloaderLowPriority ;
2018-04-19 22:27:28 +08:00
if ( options & SDWebImageProgressiveLoad ) downloaderOptions | = SDWebImageDownloaderProgressiveLoad ;
2018-04-15 16:40:42 +08:00
if ( options & SDWebImageRefreshCached ) downloaderOptions | = SDWebImageDownloaderUseNSURLCache ;
if ( options & SDWebImageContinueInBackground ) downloaderOptions | = SDWebImageDownloaderContinueInBackground ;
if ( options & SDWebImageHandleCookies ) downloaderOptions | = SDWebImageDownloaderHandleCookies ;
if ( options & SDWebImageAllowInvalidSSLCertificates ) downloaderOptions | = SDWebImageDownloaderAllowInvalidSSLCertificates ;
if ( options & SDWebImageHighPriority ) downloaderOptions | = SDWebImageDownloaderHighPriority ;
if ( options & SDWebImageScaleDownLargeImages ) downloaderOptions | = SDWebImageDownloaderScaleDownLargeImages ;
2018-08-02 11:07:53 +08:00
if ( options & SDWebImageAvoidDecodeImage ) downloaderOptions | = SDWebImageDownloaderAvoidDecodeImage ;
if ( options & SDWebImageDecodeFirstFrameOnly ) downloaderOptions | = SDWebImageDownloaderDecodeFirstFrameOnly ;
if ( options & SDWebImagePreloadAllFrames ) downloaderOptions | = SDWebImageDownloaderPreloadAllFrames ;
2018-04-15 16:40:42 +08:00
2018-04-16 00:23:03 +08:00
if ( cachedImage && options & SDWebImageRefreshCached ) {
2018-04-15 16:40:42 +08:00
// force progressive off if image already cached but forced refreshing
2018-04-19 22:27:28 +08:00
downloaderOptions & = ~ SDWebImageDownloaderProgressiveLoad ;
2018-04-15 16:40:42 +08:00
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions | = SDWebImageDownloaderIgnoreCachedResponse ;
}
return [ self downloadImageWithURL : url options : downloaderOptions context : context progress : progressBlock completed : completedBlock ] ;
}
2019-04-02 14:59:21 +08:00
- ( BOOL ) shouldBlockFailedURLWithURL : ( NSURL * ) url error : ( NSError * ) error {
BOOL shouldBlockFailedURL ;
// Filter the error domain and check error codes
if ( [ error . domain isEqualToString : SDWebImageErrorDomain ] ) {
shouldBlockFailedURL = ( error . code = = SDWebImageErrorInvalidURL
|| error . code = = SDWebImageErrorBadImageData ) ;
} else if ( [ error . domain isEqualToString : NSURLErrorDomain ] ) {
shouldBlockFailedURL = ( error . code ! = NSURLErrorNotConnectedToInternet
&& error . code ! = NSURLErrorCancelled
&& error . code ! = NSURLErrorTimedOut
&& error . code ! = NSURLErrorInternationalRoamingOff
&& error . code ! = NSURLErrorDataNotAllowed
&& error . code ! = NSURLErrorCannotFindHost
&& error . code ! = NSURLErrorCannotConnectToHost
&& error . code ! = NSURLErrorNetworkConnectionLost ) ;
} else {
shouldBlockFailedURL = NO ;
}
return shouldBlockFailedURL ;
}
2018-04-15 16:40:42 +08:00
@ end