Merge branch '5.x' into replace-valueforkey
13
CHANGELOG.md
|
@ -1,3 +1,16 @@
|
||||||
|
## [5.0.0-beta2 - Customizable SDWebImage, on Jul 31st, 2018](https://github.com/rs/SDWebImage/releases/tag/5.0.0-beta2)
|
||||||
|
See [all tickets marked for the 5.0.0 release](https://github.com/rs/SDWebImage/milestone/15)
|
||||||
|
|
||||||
|
#### Features
|
||||||
|
- Add `SDImageCoderWebImageContext` coder option, which allow custom coder plugin, to receive the context option from top-level API #2405
|
||||||
|
- Updated all existing diagrams for 5.0 release + added new ones (small detailed diagrams for the most important components) #2407
|
||||||
|
|
||||||
|
#### Fixes
|
||||||
|
- Fix nullable key for `sd_imageLoadOperationForKey` #2389
|
||||||
|
- Replace `__bridge_transfer` with `__bridge` when convert from `CFStringRef` to `NSString` #2392
|
||||||
|
- Rename `sd_UTTypeFromSDImageFormat` to `sd_UTTypeFromImageFormat` #2395
|
||||||
|
- Change `SDImageFormat` to use `NS_TYPED_EXTENSIBLE_ENUM` instead of fixed enum, to allow custom coder plugins to extend it #2400
|
||||||
|
|
||||||
## [4.4.2 - 4.4 patch, on July 18th, 2018](https://github.com/rs/SDWebImage/releases/tag/4.4.2)
|
## [4.4.2 - 4.4 patch, on July 18th, 2018](https://github.com/rs/SDWebImage/releases/tag/4.4.2)
|
||||||
See [all tickets marked for the 4.4.2 release](https://github.com/rs/SDWebImage/milestone/27)
|
See [all tickets marked for the 4.4.2 release](https://github.com/rs/SDWebImage/milestone/27)
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 191 KiB |
After Width: | Height: | Size: 320 KiB |
After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 204 KiB |
After Width: | Height: | Size: 165 KiB |
After Width: | Height: | Size: 279 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 148 KiB |
22762
Docs/SDWebImage.mdj
Before Width: | Height: | Size: 337 KiB |
Before Width: | Height: | Size: 129 KiB |
22
README.md
|
@ -178,10 +178,28 @@ All source code is licensed under the [MIT License](https://raw.github.com/rs/SD
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
|
#### High Level Diagram
|
||||||
<p align="center" >
|
<p align="center" >
|
||||||
<img src="Docs/SDWebImageClassDiagram.png" title="SDWebImage class diagram">
|
<img src="Docs/Diagrams/SDWebImageHighLevelDiagram.jpeg" title="SDWebImage high level diagram">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
#### Overall Class Diagram
|
||||||
<p align="center" >
|
<p align="center" >
|
||||||
<img src="Docs/SDWebImageSequenceDiagram.png" title="SDWebImage sequence diagram">
|
<img src="Docs/Diagrams/SDWebImageClassDiagram.png" title="SDWebImage overall class diagram">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
#### Top Level API Diagram
|
||||||
|
<p align="center" >
|
||||||
|
<img src="Docs/Diagrams/SDWebImageTopLevelClassDiagram.png" title="SDWebImage top level API diagram">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
#### Main Sequence Diagram
|
||||||
|
<p align="center" >
|
||||||
|
<img src="Docs/Diagrams/SDWebImageSequenceDiagram.png" title="SDWebImage sequence diagram">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
#### More detailed diagrams
|
||||||
|
- [Manager API Diagram](Docs/Diagrams/SDWebImageManagerClassDiagram.png)
|
||||||
|
- [Coders API Diagram](Docs/Diagrams/SDWebImageCodersClassDiagram.png)
|
||||||
|
- [Loader API Diagram](Docs/Diagrams/SDWebImageLoaderClassDiagram.png)
|
||||||
|
- [Cache API Diagram](Docs/Diagrams/SDWebImageCacheClassDiagram.png)
|
|
@ -1,6 +1,6 @@
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = 'SDWebImage'
|
s.name = 'SDWebImage'
|
||||||
s.version = '5.0.0-beta'
|
s.version = '5.0.0-beta2'
|
||||||
|
|
||||||
s.osx.deployment_target = '10.10'
|
s.osx.deployment_target = '10.10'
|
||||||
s.ios.deployment_target = '8.0'
|
s.ios.deployment_target = '8.0'
|
||||||
|
|
|
@ -10,15 +10,18 @@
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import "SDWebImageCompat.h"
|
#import "SDWebImageCompat.h"
|
||||||
|
|
||||||
typedef NS_ENUM(NSInteger, SDImageFormat) {
|
/**
|
||||||
SDImageFormatUndefined = -1,
|
You can use switch case like normal enum. It's also recommended to add a default case. You should not assume anything about the raw value.
|
||||||
SDImageFormatJPEG = 0,
|
For custom coder plugin, it can also extern the enum for supported format. See `SDImageCoder` for more detailed information.
|
||||||
SDImageFormatPNG,
|
*/
|
||||||
SDImageFormatGIF,
|
typedef NSInteger SDImageFormat NS_TYPED_EXTENSIBLE_ENUM;
|
||||||
SDImageFormatTIFF,
|
static const SDImageFormat SDImageFormatUndefined = -1;
|
||||||
SDImageFormatWebP,
|
static const SDImageFormat SDImageFormatJPEG = 0;
|
||||||
SDImageFormatHEIC
|
static const SDImageFormat SDImageFormatPNG = 1;
|
||||||
};
|
static const SDImageFormat SDImageFormatGIF = 2;
|
||||||
|
static const SDImageFormat SDImageFormatTIFF = 3;
|
||||||
|
static const SDImageFormat SDImageFormatWebP = 4;
|
||||||
|
static const SDImageFormat SDImageFormatHEIC = 5;
|
||||||
|
|
||||||
@interface NSData (ImageContentType)
|
@interface NSData (ImageContentType)
|
||||||
|
|
||||||
|
@ -37,7 +40,7 @@ typedef NS_ENUM(NSInteger, SDImageFormat) {
|
||||||
* @param format Format as SDImageFormat
|
* @param format Format as SDImageFormat
|
||||||
* @return The UTType as CFStringRef
|
* @return The UTType as CFStringRef
|
||||||
*/
|
*/
|
||||||
+ (nonnull CFStringRef)sd_UTTypeFromImageFormat:(SDImageFormat)format CF_RETURNS_NOT_RETAINED;
|
+ (nonnull CFStringRef)sd_UTTypeFromImageFormat:(SDImageFormat)format CF_RETURNS_NOT_RETAINED NS_SWIFT_NAME(sd_UTType(from:));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert UTTyppe to SDImageFormat
|
* Convert UTTyppe to SDImageFormat
|
||||||
|
|
|
@ -104,4 +104,15 @@
|
||||||
|
|
||||||
- (nonnull instancetype)init NS_UNAVAILABLE;
|
- (nonnull instancetype)init NS_UNAVAILABLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Move the cache directory from old location to new location, the old location will be removed after finish.
|
||||||
|
If the old location does not exist, does nothing.
|
||||||
|
If the new location does not exist, only do a movement of directory.
|
||||||
|
If the new location does exist, will move and merge the files from old location.
|
||||||
|
|
||||||
|
@param srcPath old location of cache directory
|
||||||
|
@param dstPath new location of cache directory
|
||||||
|
*/
|
||||||
|
- (void)moveCacheDirectoryFromPath:(nonnull NSString *)srcPath toPath:(nonnull NSString *)dstPath;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -228,6 +228,39 @@
|
||||||
return [path stringByAppendingPathComponent:filename];
|
return [path stringByAppendingPathComponent:filename];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)moveCacheDirectoryFromPath:(nonnull NSString *)srcPath toPath:(nonnull NSString *)dstPath {
|
||||||
|
NSParameterAssert(srcPath);
|
||||||
|
NSParameterAssert(dstPath);
|
||||||
|
// Check if old path is equal to new path
|
||||||
|
if ([srcPath isEqualToString:dstPath]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BOOL isDirectory;
|
||||||
|
// Check if old path is directory
|
||||||
|
if (![self.fileManager fileExistsAtPath:srcPath isDirectory:&isDirectory] || !isDirectory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check if new path is directory
|
||||||
|
if (![self.fileManager fileExistsAtPath:dstPath isDirectory:&isDirectory] || !isDirectory) {
|
||||||
|
if (!isDirectory) {
|
||||||
|
// New path is not directory, remove file
|
||||||
|
[self.fileManager removeItemAtPath:dstPath error:nil];
|
||||||
|
}
|
||||||
|
// New directory does not exist, rename directory
|
||||||
|
[self.fileManager moveItemAtPath:srcPath toPath:dstPath error:nil];
|
||||||
|
} else {
|
||||||
|
// New directory exist, merge the files
|
||||||
|
NSDirectoryEnumerator *dirEnumerator = [self.fileManager enumeratorAtPath:srcPath];
|
||||||
|
NSString *file;
|
||||||
|
while ((file = [dirEnumerator nextObject])) {
|
||||||
|
// Don't handle error, just try to move.
|
||||||
|
[self.fileManager moveItemAtPath:[srcPath stringByAppendingPathComponent:file] toPath:[dstPath stringByAppendingPathComponent:file] error:nil];
|
||||||
|
}
|
||||||
|
// Remove the old path
|
||||||
|
[self.fileManager removeItemAtPath:srcPath error:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Hash
|
#pragma mark - Hash
|
||||||
|
|
||||||
static inline NSString * _Nullable SDDiskCacheFileNameForKey(NSString * _Nullable key) {
|
static inline NSString * _Nullable SDDiskCacheFileNameForKey(NSString * _Nullable key) {
|
||||||
|
|
|
@ -86,6 +86,9 @@
|
||||||
|
|
||||||
NSAssert([config.diskCacheClass conformsToProtocol:@protocol(SDDiskCache)], @"Custom disk cache class must conform to `SDDiskCache` protocol");
|
NSAssert([config.diskCacheClass conformsToProtocol:@protocol(SDDiskCache)], @"Custom disk cache class must conform to `SDDiskCache` protocol");
|
||||||
_diskCache = [[config.diskCacheClass alloc] initWithCachePath:_diskCachePath config:_config];
|
_diskCache = [[config.diskCacheClass alloc] initWithCachePath:_diskCachePath config:_config];
|
||||||
|
|
||||||
|
// Check and migrate disk cache directory if need
|
||||||
|
[self migrateDiskCacheDirectory];
|
||||||
|
|
||||||
#if SD_UIKIT
|
#if SD_UIKIT
|
||||||
// Subscribe to app events
|
// Subscribe to app events
|
||||||
|
@ -123,9 +126,22 @@
|
||||||
return [self.diskCache cachePathForKey:key];
|
return [self.diskCache cachePathForKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
|
- (nullable NSString *)makeDiskCachePath:(nonnull NSString *)fullNamespace {
|
||||||
NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
||||||
return [paths[0] stringByAppendingPathComponent:fullNamespace];
|
return [paths.firstObject stringByAppendingPathComponent:fullNamespace];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)migrateDiskCacheDirectory {
|
||||||
|
if ([self.diskCache isKindOfClass:[SDDiskCache class]]) {
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
NSString *newDefaultPath = [[self makeDiskCachePath:@"default"] stringByAppendingPathComponent:@"com.hackemist.SDImageCache.default"];
|
||||||
|
NSString *oldDefaultPath = [[self makeDiskCachePath:@"default"] stringByAppendingPathComponent:@"com.hackemist.SDWebImageCache.default"];
|
||||||
|
dispatch_async(self.ioQueue, ^{
|
||||||
|
[((SDDiskCache *)self.diskCache) moveCacheDirectoryFromPath:oldDefaultPath toPath:newDefaultPath];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Store Ops
|
#pragma mark - Store Ops
|
||||||
|
|
|
@ -31,7 +31,13 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!image) {
|
if (!image) {
|
||||||
image = [[SDImageCodersManager sharedManager] decodedImageWithData:imageData options:@{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)}];
|
SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
|
||||||
|
if (context) {
|
||||||
|
SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
|
||||||
|
[mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
|
||||||
|
coderOptions = [mutableCoderOptions copy];
|
||||||
|
}
|
||||||
|
image = [[SDImageCodersManager sharedManager] decodedImageWithData:imageData options:coderOptions];
|
||||||
}
|
}
|
||||||
if (image) {
|
if (image) {
|
||||||
BOOL shouldDecode = (options & SDWebImageAvoidDecodeImage) == 0;
|
BOOL shouldDecode = (options & SDWebImageAvoidDecodeImage) == 0;
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
typedef NSString * SDImageCoderOption NS_STRING_ENUM;
|
typedef NSString * SDImageCoderOption NS_STRING_ENUM;
|
||||||
typedef NSDictionary<SDImageCoderOption, id> SDImageCoderOptions;
|
typedef NSDictionary<SDImageCoderOption, id> SDImageCoderOptions;
|
||||||
|
typedef NSMutableDictionary<SDImageCoderOption, id> SDImageCoderMutableOptions;
|
||||||
|
|
||||||
#pragma mark - Coder Options
|
#pragma mark - Coder Options
|
||||||
// These options are for image decoding
|
// These options are for image decoding
|
||||||
|
@ -38,6 +39,14 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeFirstFrame
|
||||||
*/
|
*/
|
||||||
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeCompressionQuality;
|
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeCompressionQuality;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A SDWebImageContext object which hold the original context options from top-level API. (SDWebImageContext)
|
||||||
|
This option is ignored for all built-in coders and take no effect.
|
||||||
|
But this may be useful for some custom coders, because some business logic may dependent on things other than image or image data inforamtion only.
|
||||||
|
See `SDWebImageContext` for more detailed information.
|
||||||
|
*/
|
||||||
|
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderWebImageContext;
|
||||||
|
|
||||||
#pragma mark - Coder
|
#pragma mark - Coder
|
||||||
/**
|
/**
|
||||||
This is the image coder protocol to provide custom image decoding/encoding.
|
This is the image coder protocol to provide custom image decoding/encoding.
|
||||||
|
@ -72,11 +81,15 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeCompressio
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns YES if this coder can encode some image. Otherwise, it should be passed to another coder.
|
Returns YES if this coder can encode some image. Otherwise, it should be passed to another coder.
|
||||||
|
For custom coder which introduce new image format, you'd better define a new `SDImageFormat` using like this. If you're creating public coder plugin for new image format, also update `https://github.com/rs/SDWebImage/wiki/Coder-Plugin-List` to avoid same value been defined twice.
|
||||||
|
* @code
|
||||||
|
static const SDImageFormat SDImageFormatHEIF = 10;
|
||||||
|
* @endcode
|
||||||
|
|
||||||
@param format The image format
|
@param format The image format
|
||||||
@return YES if this coder can encode the image, NO otherwise
|
@return YES if this coder can encode the image, NO otherwise
|
||||||
*/
|
*/
|
||||||
- (BOOL)canEncodeToFormat:(SDImageFormat)format;
|
- (BOOL)canEncodeToFormat:(SDImageFormat)format NS_SWIFT_NAME(canEncode(to:));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Encode the image to image data.
|
Encode the image to image data.
|
||||||
|
|
|
@ -13,3 +13,5 @@ SDImageCoderOption const SDImageCoderDecodeScaleFactor = @"decodeScaleFactor";
|
||||||
|
|
||||||
SDImageCoderOption const SDImageCoderEncodeFirstFrameOnly = @"encodeFirstFrameOnly";
|
SDImageCoderOption const SDImageCoderEncodeFirstFrameOnly = @"encodeFirstFrameOnly";
|
||||||
SDImageCoderOption const SDImageCoderEncodeCompressionQuality = @"encodeCompressionQuality";
|
SDImageCoderOption const SDImageCoderEncodeCompressionQuality = @"encodeCompressionQuality";
|
||||||
|
|
||||||
|
SDImageCoderOption const SDImageCoderWebImageContext = @"webImageContext";
|
||||||
|
|
|
@ -45,7 +45,13 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!image) {
|
if (!image) {
|
||||||
image = [[SDImageCodersManager sharedManager] decodedImageWithData:imageData options:@{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)}];
|
SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
|
||||||
|
if (context) {
|
||||||
|
SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
|
||||||
|
[mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
|
||||||
|
coderOptions = [mutableCoderOptions copy];
|
||||||
|
}
|
||||||
|
image = [[SDImageCodersManager sharedManager] decodedImageWithData:imageData options:coderOptions];
|
||||||
}
|
}
|
||||||
if (image) {
|
if (image) {
|
||||||
BOOL shouldDecode = (options & SDWebImageAvoidDecodeImage) == 0;
|
BOOL shouldDecode = (options & SDWebImageAvoidDecodeImage) == 0;
|
||||||
|
@ -115,7 +121,13 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!image) {
|
if (!image) {
|
||||||
image = [progressiveCoder incrementalDecodedImageWithOptions:@{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)}];
|
SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
|
||||||
|
if (context) {
|
||||||
|
SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
|
||||||
|
[mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
|
||||||
|
coderOptions = [mutableCoderOptions copy];
|
||||||
|
}
|
||||||
|
image = [progressiveCoder incrementalDecodedImageWithOptions:coderOptions];
|
||||||
}
|
}
|
||||||
if (image) {
|
if (image) {
|
||||||
BOOL shouldDecode = (options & SDWebImageAvoidDecodeImage) == 0;
|
BOOL shouldDecode = (options & SDWebImageAvoidDecodeImage) == 0;
|
||||||
|
|
|
@ -84,6 +84,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
|
||||||
// `setObject:forKey:` just call this with 0 cost. Override this is enough
|
// `setObject:forKey:` just call this with 0 cost. Override this is enough
|
||||||
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
|
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
|
||||||
[super setObject:obj forKey:key cost:g];
|
[super setObject:obj forKey:key cost:g];
|
||||||
|
if (!self.config.shouldUseWeakMemoryCache) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (key && obj) {
|
if (key && obj) {
|
||||||
// Store weak cache
|
// Store weak cache
|
||||||
LOCK(self.weakCacheLock);
|
LOCK(self.weakCacheLock);
|
||||||
|
@ -94,6 +97,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
|
||||||
|
|
||||||
- (id)objectForKey:(id)key {
|
- (id)objectForKey:(id)key {
|
||||||
id obj = [super objectForKey:key];
|
id obj = [super objectForKey:key];
|
||||||
|
if (!self.config.shouldUseWeakMemoryCache) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
if (key && !obj) {
|
if (key && !obj) {
|
||||||
// Check weak cache
|
// Check weak cache
|
||||||
LOCK(self.weakCacheLock);
|
LOCK(self.weakCacheLock);
|
||||||
|
@ -113,6 +119,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
|
||||||
|
|
||||||
- (void)removeObjectForKey:(id)key {
|
- (void)removeObjectForKey:(id)key {
|
||||||
[super removeObjectForKey:key];
|
[super removeObjectForKey:key];
|
||||||
|
if (!self.config.shouldUseWeakMemoryCache) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (key) {
|
if (key) {
|
||||||
// Remove weak cache
|
// Remove weak cache
|
||||||
LOCK(self.weakCacheLock);
|
LOCK(self.weakCacheLock);
|
||||||
|
@ -123,6 +132,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
|
||||||
|
|
||||||
- (void)removeAllObjects {
|
- (void)removeAllObjects {
|
||||||
[super removeAllObjects];
|
[super removeAllObjects];
|
||||||
|
if (!self.config.shouldUseWeakMemoryCache) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Manually remove should also remove weak cache
|
// Manually remove should also remove weak cache
|
||||||
LOCK(self.weakCacheLock);
|
LOCK(self.weakCacheLock);
|
||||||
[self.weakCache removeAllObjects];
|
[self.weakCache removeAllObjects];
|
||||||
|
|
|
@ -154,88 +154,6 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
|
||||||
context:(nullable SDWebImageContext *)context
|
context:(nullable SDWebImageContext *)context
|
||||||
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
||||||
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
|
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
|
||||||
__weak SDWebImageDownloader *wself = self;
|
|
||||||
|
|
||||||
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^NSOperation<SDWebImageDownloaderOperation> *{
|
|
||||||
__strong __typeof (wself) sself = wself;
|
|
||||||
NSTimeInterval timeoutInterval = sself.config.downloadTimeout;
|
|
||||||
if (timeoutInterval == 0.0) {
|
|
||||||
timeoutInterval = 15.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
|
|
||||||
mutableRequest.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
|
|
||||||
mutableRequest.HTTPShouldUsePipelining = YES;
|
|
||||||
mutableRequest.allHTTPHeaderFields = sself.HTTPHeaders;
|
|
||||||
id<SDWebImageDownloaderRequestModifier> requestModifier = context[SDWebImageContextDownloadRequestModifier];
|
|
||||||
if (!requestModifier) {
|
|
||||||
requestModifier = self.requestModifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
Class operationClass = sself.config.operationClass;
|
|
||||||
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) {
|
|
||||||
// Custom operation class
|
|
||||||
} else {
|
|
||||||
operationClass = [SDWebImageDownloaderOperation class];
|
|
||||||
}
|
|
||||||
NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:sself.session options:options context:context];
|
|
||||||
|
|
||||||
if (sself.config.urlCredential) {
|
|
||||||
operation.credential = sself.config.urlCredential;
|
|
||||||
} else if (sself.config.username && sself.config.password) {
|
|
||||||
operation.credential = [NSURLCredential credentialWithUser:sself.config.username password:sself.config.password persistence:NSURLCredentialPersistenceForSession];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options & SDWebImageDownloaderHighPriority) {
|
|
||||||
operation.queuePriority = NSOperationQueuePriorityHigh;
|
|
||||||
} else if (options & SDWebImageDownloaderLowPriority) {
|
|
||||||
operation.queuePriority = NSOperationQueuePriorityLow;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sself.config.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
|
|
||||||
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
|
|
||||||
[sself.lastAddedOperation addDependency:operation];
|
|
||||||
sself.lastAddedOperation = operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
return operation;
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)cancel:(nullable SDWebImageDownloadToken *)token {
|
|
||||||
NSURL *url = token.url;
|
|
||||||
if (!url) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LOCK(self.operationsLock);
|
|
||||||
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
|
|
||||||
if (operation) {
|
|
||||||
BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
|
|
||||||
if (canceled) {
|
|
||||||
[self.URLOperations removeObjectForKey:url];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UNLOCK(self.operationsLock);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
|
|
||||||
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
|
|
||||||
forURL:(nullable NSURL *)url
|
|
||||||
createCallback:(NSOperation<SDWebImageDownloaderOperation> *(^)(void))createCallback {
|
|
||||||
// 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.
|
// 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.
|
||||||
if (url == nil) {
|
if (url == nil) {
|
||||||
if (completedBlock) {
|
if (completedBlock) {
|
||||||
|
@ -248,8 +166,8 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
|
||||||
LOCK(self.operationsLock);
|
LOCK(self.operationsLock);
|
||||||
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
|
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
|
||||||
if (!operation || operation.isFinished) {
|
if (!operation || operation.isFinished) {
|
||||||
// There is a case that the operation may be marked as finished, but not been removed from `self.URLOperations`.
|
// There is a case that the operation may be marked as finished, but not been removed from `self.URLOperations`.
|
||||||
operation = createCallback();
|
operation = [self createDownloaderOperationWithUrl:url options:options context:context];
|
||||||
if (!operation) {
|
if (!operation) {
|
||||||
UNLOCK(self.operationsLock);
|
UNLOCK(self.operationsLock);
|
||||||
if (completedBlock) {
|
if (completedBlock) {
|
||||||
|
@ -274,7 +192,7 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
|
||||||
[self.downloadQueue addOperation:operation];
|
[self.downloadQueue addOperation:operation];
|
||||||
}
|
}
|
||||||
UNLOCK(self.operationsLock);
|
UNLOCK(self.operationsLock);
|
||||||
|
|
||||||
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
|
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
|
||||||
|
|
||||||
SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
|
SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
|
||||||
|
@ -283,10 +201,96 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
|
||||||
token.request = operation.request;
|
token.request = operation.request;
|
||||||
token.downloadOperationCancelToken = downloadOperationCancelToken;
|
token.downloadOperationCancelToken = downloadOperationCancelToken;
|
||||||
token.downloader = self;
|
token.downloader = self;
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url
|
||||||
|
options:(SDWebImageDownloaderOptions)options
|
||||||
|
context:(nullable SDWebImageContext *)context {
|
||||||
|
NSTimeInterval timeoutInterval = self.config.downloadTimeout;
|
||||||
|
if (timeoutInterval == 0.0) {
|
||||||
|
timeoutInterval = 15.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
|
||||||
|
mutableRequest.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
|
||||||
|
mutableRequest.HTTPShouldUsePipelining = YES;
|
||||||
|
mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
|
||||||
|
id<SDWebImageDownloaderRequestModifier> requestModifier;
|
||||||
|
if ([context valueForKey:SDWebImageContextDownloadRequestModifier]) {
|
||||||
|
requestModifier = [context valueForKey:SDWebImageContextDownloadRequestModifier];
|
||||||
|
} else {
|
||||||
|
requestModifier = self.requestModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
Class operationClass = self.config.operationClass;
|
||||||
|
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) {
|
||||||
|
// Custom operation class
|
||||||
|
} else {
|
||||||
|
operationClass = [SDWebImageDownloaderOperation class];
|
||||||
|
}
|
||||||
|
NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];
|
||||||
|
|
||||||
|
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:)]) {
|
||||||
|
NSTimeInterval minimumProgressInterval = self.config.minimumProgressInterval;
|
||||||
|
minimumProgressInterval = MIN(MAX(minimumProgressInterval, 0), 1);
|
||||||
|
operation.minimumProgressInterval = minimumProgressInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options & SDWebImageDownloaderHighPriority) {
|
||||||
|
operation.queuePriority = NSOperationQueuePriorityHigh;
|
||||||
|
} else if (options & SDWebImageDownloaderLowPriority) {
|
||||||
|
operation.queuePriority = NSOperationQueuePriorityLow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.config.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
|
||||||
|
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
|
||||||
|
[self.lastAddedOperation addDependency:operation];
|
||||||
|
self.lastAddedOperation = operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)cancel:(nullable SDWebImageDownloadToken *)token {
|
||||||
|
NSURL *url = token.url;
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOCK(self.operationsLock);
|
||||||
|
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
|
||||||
|
if (operation) {
|
||||||
|
BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
|
||||||
|
if (canceled) {
|
||||||
|
[self.URLOperations removeObjectForKey:url];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UNLOCK(self.operationsLock);
|
||||||
|
}
|
||||||
|
|
||||||
- (void)cancelAllDownloads {
|
- (void)cancelAllDownloads {
|
||||||
[self.downloadQueue cancelAllOperations];
|
[self.downloadQueue cancelAllOperations];
|
||||||
}
|
}
|
||||||
|
@ -480,6 +484,9 @@ didReceiveResponse:(NSURLResponse *)response
|
||||||
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
|
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
|
||||||
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
|
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
|
||||||
if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
|
if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
|
||||||
|
if (options & SDWebImageAvoidDecodeImage) downloaderOptions |= SDWebImageDownloaderAvoidDecodeImage;
|
||||||
|
if (options & SDWebImageDecodeFirstFrameOnly) downloaderOptions |= SDWebImageDownloaderDecodeFirstFrameOnly;
|
||||||
|
if (options & SDWebImagePreloadAllFrames) downloaderOptions |= SDWebImageDownloaderPreloadAllFrames;
|
||||||
|
|
||||||
if (cachedImage && options & SDWebImageRefreshCached) {
|
if (cachedImage && options & SDWebImageRefreshCached) {
|
||||||
// force progressive off if image already cached but forced refreshing
|
// force progressive off if image already cached but forced refreshing
|
||||||
|
|
|
@ -41,6 +41,15 @@ typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, assign) NSTimeInterval downloadTimeout;
|
@property (nonatomic, assign) NSTimeInterval downloadTimeout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum interval about progress percent during network downloading. Which means the next progress callback and current progress callback's progress percent difference should be larger or equal to this value.
|
||||||
|
* The value should be 0.0-1.0.
|
||||||
|
* @note If you're using progressive decoding feature, this will also effect the image refresh rate.
|
||||||
|
* @note This value may enhance the performance if you don't want progress callback too frequently.
|
||||||
|
* Defaults to 0, which means each time we receive the new data from URLSession, we callback the progressBlock immediately.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, assign) NSTimeInterval minimumProgressInterval;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The custom session configuration in use by NSURLSession. If you don't provide one, we will use `defaultSessionConfiguration` instead.
|
* The custom session configuration in use by NSURLSession. If you don't provide one, we will use `defaultSessionConfiguration` instead.
|
||||||
* Defatuls to nil.
|
* Defatuls to nil.
|
||||||
|
|
|
@ -34,6 +34,7 @@ static SDWebImageDownloaderConfig * _defaultDownloaderConfig;
|
||||||
SDWebImageDownloaderConfig *config = [[[self class] allocWithZone:zone] init];
|
SDWebImageDownloaderConfig *config = [[[self class] allocWithZone:zone] init];
|
||||||
config.maxConcurrentDownloads = self.maxConcurrentDownloads;
|
config.maxConcurrentDownloads = self.maxConcurrentDownloads;
|
||||||
config.downloadTimeout = self.downloadTimeout;
|
config.downloadTimeout = self.downloadTimeout;
|
||||||
|
config.minimumProgressInterval = self.minimumProgressInterval;
|
||||||
config.sessionConfiguration = [self.sessionConfiguration copyWithZone:zone];
|
config.sessionConfiguration = [self.sessionConfiguration copyWithZone:zone];
|
||||||
config.operationClass = self.operationClass;
|
config.operationClass = self.operationClass;
|
||||||
config.executionOrder = self.executionOrder;
|
config.executionOrder = self.executionOrder;
|
||||||
|
|
|
@ -36,9 +36,6 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification
|
||||||
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
||||||
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
|
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
|
||||||
|
|
||||||
- (nullable NSURLCredential *)credential;
|
|
||||||
- (void)setCredential:(nullable NSURLCredential *)value;
|
|
||||||
|
|
||||||
- (BOOL)cancel:(nullable id)token;
|
- (BOOL)cancel:(nullable id)token;
|
||||||
|
|
||||||
- (nullable NSURLRequest *)request;
|
- (nullable NSURLRequest *)request;
|
||||||
|
@ -46,6 +43,10 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification
|
||||||
|
|
||||||
@optional
|
@optional
|
||||||
- (nullable NSURLSessionTask *)dataTask;
|
- (nullable NSURLSessionTask *)dataTask;
|
||||||
|
- (nullable NSURLCredential *)credential;
|
||||||
|
- (void)setCredential:(nullable NSURLCredential *)credential;
|
||||||
|
- (NSTimeInterval)minimumProgressInterval;
|
||||||
|
- (void)setMinimumProgressInterval:(NSTimeInterval)minimumProgressInterval;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -74,6 +75,15 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, strong, nullable) NSURLCredential *credential;
|
@property (nonatomic, strong, nullable) NSURLCredential *credential;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum interval about progress percent during network downloading. Which means the next progress callback and current progress callback's progress percent difference should be larger or equal to this value.
|
||||||
|
* The value should be 0.0-1.0.
|
||||||
|
* @note If you're using progressive decoding feature, this will also effect the image refresh rate.
|
||||||
|
* @note This value may enhance the performance if you don't want progress callback too frequently.
|
||||||
|
* Defaults to 0, which means each time we receive the new data from URLSession, we callback the progressBlock immediately.
|
||||||
|
*/
|
||||||
|
@property (assign, nonatomic) NSTimeInterval minimumProgressInterval;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The options for the receiver.
|
* The options for the receiver.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -37,9 +37,11 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
|
||||||
@property (assign, nonatomic, getter = isFinished) BOOL finished;
|
@property (assign, nonatomic, getter = isFinished) BOOL finished;
|
||||||
@property (strong, nonatomic, nullable) NSMutableData *imageData;
|
@property (strong, nonatomic, nullable) NSMutableData *imageData;
|
||||||
@property (copy, nonatomic, nullable) NSData *cachedData; // for `SDWebImageDownloaderIgnoreCachedResponse`
|
@property (copy, nonatomic, nullable) NSData *cachedData; // for `SDWebImageDownloaderIgnoreCachedResponse`
|
||||||
@property (assign, nonatomic, readwrite) NSUInteger expectedSize;
|
@property (assign, nonatomic) NSUInteger expectedSize; // may be 0
|
||||||
|
@property (assign, nonatomic) NSUInteger receivedSize;
|
||||||
@property (strong, nonatomic, nullable, readwrite) NSURLResponse *response;
|
@property (strong, nonatomic, nullable, readwrite) NSURLResponse *response;
|
||||||
@property (strong, nonatomic, nullable) NSError *responseError;
|
@property (strong, nonatomic, nullable) NSError *responseError;
|
||||||
|
@property (assign, nonatomic) double previousProgress; // previous progress percent
|
||||||
|
|
||||||
// This is weak because it is injected by whoever manages this session. If this gets nil-ed out, we won't be able to run
|
// This is weak because it is injected by whoever manages this session. If this gets nil-ed out, we won't be able to run
|
||||||
// the task associated with this operation
|
// the task associated with this operation
|
||||||
|
@ -336,17 +338,37 @@ didReceiveResponse:(NSURLResponse *)response
|
||||||
self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize];
|
self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize];
|
||||||
}
|
}
|
||||||
[self.imageData appendData:data];
|
[self.imageData appendData:data];
|
||||||
|
|
||||||
|
self.receivedSize = self.imageData.length;
|
||||||
|
if (self.expectedSize == 0) {
|
||||||
|
// Unknown expectedSize, immediately call progressBlock and return
|
||||||
|
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
|
||||||
|
progressBlock(self.receivedSize, self.expectedSize, self.request.URL);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the finish status
|
||||||
|
BOOL finished = (self.receivedSize >= self.expectedSize);
|
||||||
|
// Get the current progress
|
||||||
|
double currentProgress = (double)self.receivedSize / (double)self.expectedSize;
|
||||||
|
double previousProgress = self.previousProgress;
|
||||||
|
// Check if we need callback progress
|
||||||
|
if (currentProgress - previousProgress < self.minimumProgressInterval) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.previousProgress = currentProgress;
|
||||||
|
|
||||||
if ((self.options & SDWebImageDownloaderProgressiveLoad) && self.expectedSize > 0) {
|
if (self.options & SDWebImageDownloaderProgressiveLoad) {
|
||||||
// Get the image data
|
// Get the image data
|
||||||
NSData *imageData = [self.imageData copy];
|
NSData *imageData = [self.imageData copy];
|
||||||
// Get the total bytes downloaded
|
|
||||||
const NSUInteger totalSize = imageData.length;
|
|
||||||
// Get the finish status
|
|
||||||
BOOL finished = (totalSize >= self.expectedSize);
|
|
||||||
|
|
||||||
// progressive decode the image in coder queue
|
// progressive decode the image in coder queue
|
||||||
dispatch_async(self.coderQueue, ^{
|
dispatch_async(self.coderQueue, ^{
|
||||||
|
// If all the data has already been downloaded, earily return to avoid further decoding
|
||||||
|
if (self.receivedSize >= self.expectedSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
UIImage *image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, finished, self, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
|
UIImage *image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, finished, self, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
|
||||||
if (image) {
|
if (image) {
|
||||||
// We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding.
|
// We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding.
|
||||||
|
@ -355,9 +377,9 @@ didReceiveResponse:(NSURLResponse *)response
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
|
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
|
||||||
progressBlock(self.imageData.length, self.expectedSize, self.request.URL);
|
progressBlock(self.receivedSize, self.expectedSize, self.request.URL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,8 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface SDImageCacheTests : SDTestCase
|
@interface SDImageCacheTests : SDTestCase <NSFileManagerDelegate>
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation SDImageCacheTests
|
@implementation SDImageCacheTests
|
||||||
|
@ -359,6 +360,25 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
|
||||||
expect([diskCache isKindOfClass:[SDWebImageTestDiskCache class]]).to.beTruthy();
|
expect([diskCache isKindOfClass:[SDWebImageTestDiskCache class]]).to.beTruthy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)test44DiskCacheMigrationFromOldVersion {
|
||||||
|
SDImageCacheConfig *config = [[SDImageCacheConfig alloc] init];
|
||||||
|
NSFileManager *fileManager = [[NSFileManager alloc] init];
|
||||||
|
config.fileManager = fileManager;
|
||||||
|
|
||||||
|
// Fake to store a.png into old path
|
||||||
|
NSString *newDefaultPath = [[self makeDiskCachePath:@"default"] stringByAppendingPathComponent:@"com.hackemist.SDImageCache.default"];
|
||||||
|
NSString *oldDefaultPath = [[self makeDiskCachePath:@"default"] stringByAppendingPathComponent:@"com.hackemist.SDWebImageCache.default"];
|
||||||
|
[fileManager createDirectoryAtPath:oldDefaultPath withIntermediateDirectories:YES attributes:nil error:nil];
|
||||||
|
[fileManager createFileAtPath:[oldDefaultPath stringByAppendingPathComponent:@"a.png"] contents:[NSData dataWithContentsOfFile:[self testPNGPath]] attributes:nil];
|
||||||
|
// Call migration
|
||||||
|
SDDiskCache *diskCache = [[SDDiskCache alloc] initWithCachePath:newDefaultPath config:config];
|
||||||
|
[diskCache moveCacheDirectoryFromPath:oldDefaultPath toPath:newDefaultPath];
|
||||||
|
|
||||||
|
// Expect a.png into new path
|
||||||
|
BOOL exist = [fileManager fileExistsAtPath:[newDefaultPath stringByAppendingPathComponent:@"a.png"]];
|
||||||
|
expect(exist).beTruthy();
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - SDImageCache & SDImageCachesManager
|
#pragma mark - SDImageCache & SDImageCachesManager
|
||||||
- (void)test50SDImageCacheQueryOp {
|
- (void)test50SDImageCacheQueryOp {
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache query op works"];
|
XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache query op works"];
|
||||||
|
|
|
@ -21,11 +21,6 @@
|
||||||
|
|
||||||
@interface SDWebImageDownloader ()
|
@interface SDWebImageDownloader ()
|
||||||
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
|
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
|
||||||
|
|
||||||
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
|
|
||||||
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
|
|
||||||
forURL:(nullable NSURL *)url
|
|
||||||
createCallback:(NSOperation<SDWebImageDownloaderOperation> *(^)(void))createCallback;
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,16 +99,15 @@
|
||||||
[downloader invalidateSessionAndCancel:YES];
|
[downloader invalidateSessionAndCancel:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)test07ThatAddProgressCallbackCompletedBlockWithNilURLCallsTheCompletionBlockWithNils {
|
- (void)test07ThatDownloadImageWithNilURLCallsCompletionWithNils {
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion is called with nils"];
|
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion is called with nils"];
|
||||||
[[SDWebImageDownloader sharedDownloader] addProgressCallback:nil completedBlock:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
|
[[SDWebImageDownloader sharedDownloader] downloadImageWithURL:nil options:0 progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
|
||||||
if (!image && !data && error) {
|
expect(image).to.beNil();
|
||||||
[expectation fulfill];
|
expect(data).to.beNil();
|
||||||
} else {
|
expect(error.code).equal(SDWebImageErrorInvalidURL);
|
||||||
XCTFail(@"All params except error should be nil");
|
[expectation fulfill];
|
||||||
}
|
}];
|
||||||
} forURL:nil createCallback:nil];
|
[self waitForExpectationsWithCommonTimeout];
|
||||||
[self waitForExpectationsWithTimeout:0.5 handler:nil];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)test08ThatAHTTPAuthDownloadWorks {
|
- (void)test08ThatAHTTPAuthDownloadWorks {
|
||||||
|
@ -265,6 +259,34 @@
|
||||||
[self waitForExpectationsWithCommonTimeout];
|
[self waitForExpectationsWithCommonTimeout];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)test17ThatMinimumProgressIntervalWorks {
|
||||||
|
XCTestExpectation *expectation = [self expectationWithDescription:@"Minimum progress interval"];
|
||||||
|
SDWebImageDownloaderConfig *config = SDWebImageDownloaderConfig.defaultDownloaderConfig;
|
||||||
|
config.minimumProgressInterval = 0.51; // This will make the progress only callback once
|
||||||
|
SDWebImageDownloader *downloader = [[SDWebImageDownloader alloc] initWithConfig:config];
|
||||||
|
NSURL *imageURL = [NSURL URLWithString:@"http://www.ioncannon.net/wp-content/uploads/2011/06/test2.webp"];
|
||||||
|
__block NSUInteger allProgressCount = 0; // All progress (including operation start / first HTTP response, etc)
|
||||||
|
__block NSUInteger validProgressCount = 0; // Only progress from `URLSession:dataTask:didReceiveData:`
|
||||||
|
[downloader downloadImageWithURL:imageURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
|
||||||
|
allProgressCount++;
|
||||||
|
if (expectedSize <= 0 || receivedSize <= 0) {
|
||||||
|
// ignore the progress callback until we receive data
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
validProgressCount++;
|
||||||
|
} completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
|
||||||
|
if (allProgressCount > 1 && validProgressCount == 1) {
|
||||||
|
[expectation fulfill];
|
||||||
|
} else {
|
||||||
|
XCTFail(@"Progress callback more than once");
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
[self waitForExpectationsWithCommonTimeoutUsingHandler:^(NSError * _Nullable error) {
|
||||||
|
[downloader invalidateSessionAndCancel:YES];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per #883 - Fix multiple requests for same image and then canceling one
|
* Per #883 - Fix multiple requests for same image and then canceling one
|
||||||
* Old SDWebImage (3.x) could not handle correctly multiple requests for the same image + cancel
|
* Old SDWebImage (3.x) could not handle correctly multiple requests for the same image + cancel
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
@interface SDWebImageTestDownloadOperation : NSOperation <SDWebImageDownloaderOperation>
|
@interface SDWebImageTestDownloadOperation : NSOperation <SDWebImageDownloaderOperation>
|
||||||
|
|
||||||
@property (nonatomic, strong, nullable) NSURLCredential *credential;
|
|
||||||
@property (nonatomic, strong, nullable) NSURLRequest *request;
|
@property (nonatomic, strong, nullable) NSURLRequest *request;
|
||||||
@property (nonatomic, strong, nullable) NSURLResponse *response;
|
@property (nonatomic, strong, nullable) NSURLResponse *response;
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,11 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>FMWK</string>
|
<string>FMWK</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>5.0.0-beta</string>
|
<string>5.0.0-beta2</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>5.0.0-beta</string>
|
<string>5.0.0-beta2</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|