Merge branch 'master' into gif_bk

This commit is contained in:
Insomnia 2020-11-20 16:02:18 +08:00
commit c69e80286b
23 changed files with 250 additions and 141 deletions

View File

@ -1,3 +1,9 @@
## [5.9.5 - 5.9 Patch, on Nov 13th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.9.5)
See [all tickets marked for the 5.9.5 release](https://github.com/SDWebImage/SDWebImage/milestone/81)
### Fixes
- Add animationImages support when using SDAnimatedImageView #3113
## [5.9.4 - 5.9 Patch, on Oct 13th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.9.4)
See [all tickets marked for the 5.9.4 release](https://github.com/SDWebImage/SDWebImage/milestone/80)

View File

@ -295,6 +295,9 @@ It's also recommend to use the module import syntax, available for CocoaPods(ena
At this point your workspace should build without error. If you are having problem, post to the Issue and the
community can help you solve it.
## Data Collection Practices
As required by the [App privacy details on the App Store](https://developer.apple.com/app-store/app-privacy-details/), here's SDWebImage's list of [Data Collection Practices](https://sdwebimage.github.io/DataCollection/index.html).
## Author
- [Olivier Poitrey](https://github.com/rs)

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'SDWebImage'
s.version = '5.9.4'
s.version = '5.9.5'
s.osx.deployment_target = '10.10'
s.ios.deployment_target = '8.0'

View File

@ -13,6 +13,7 @@
#import "SDInternalMacros.h"
@interface SDAnimatedImagePlayer () {
SD_LOCK_DECLARE(_lock);
NSRunLoopMode _runLoopMode;
}
@ -27,7 +28,6 @@
@property (nonatomic, assign) BOOL shouldReverse;
@property (nonatomic, assign) NSUInteger maxBufferCount;
@property (nonatomic, strong) NSOperationQueue *fetchQueue;
@property (nonatomic, strong) dispatch_semaphore_t lock;
@property (nonatomic, strong) SDDisplayLink *displayLink;
@end
@ -47,6 +47,7 @@
self.totalLoopCount = provider.animatedImageLoopCount;
self.animatedProvider = provider;
self.playbackRate = 1.0;
SD_LOCK_INIT(_lock);
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
@ -71,7 +72,7 @@
[_fetchQueue cancelAllOperations];
[_fetchQueue addOperationWithBlock:^{
NSNumber *currentFrameIndex = @(self.currentFrameIndex);
SD_LOCK(self.lock);
SD_LOCK(self->_lock);
NSArray *keys = self.frameBuffer.allKeys;
// only keep the next frame for later rendering
for (NSNumber * key in keys) {
@ -79,7 +80,7 @@
[self.frameBuffer removeObjectForKey:key];
}
}
SD_UNLOCK(self.lock);
SD_UNLOCK(self->_lock);
}];
}
@ -99,13 +100,6 @@
return _frameBuffer;
}
- (dispatch_semaphore_t)lock {
if (!_lock) {
_lock = dispatch_semaphore_create(1);
}
return _lock;
}
- (SDDisplayLink *)displayLink {
if (!_displayLink) {
_displayLink = [SDDisplayLink displayLinkWithTarget:self selector:@selector(displayDidRefresh:)];
@ -158,9 +152,9 @@
#endif
if (posterFrame) {
self.currentFrame = posterFrame;
SD_LOCK(self.lock);
SD_LOCK(self->_lock);
self.frameBuffer[@(self.currentFrameIndex)] = self.currentFrame;
SD_UNLOCK(self.lock);
SD_UNLOCK(self->_lock);
[self handleFrameChange];
}
}
@ -178,9 +172,9 @@
}
- (void)clearFrameBuffer {
SD_LOCK(self.lock);
SD_LOCK(_lock);
[_frameBuffer removeAllObjects];
SD_UNLOCK(self.lock);
SD_UNLOCK(_lock);
}
#pragma mark - Animation Control
@ -266,13 +260,13 @@
BOOL bufferFull = NO;
if (self.needsDisplayWhenImageBecomesAvailable) {
UIImage *currentFrame;
SD_LOCK(self.lock);
SD_LOCK(_lock);
currentFrame = self.frameBuffer[@(currentFrameIndex)];
SD_UNLOCK(self.lock);
SD_UNLOCK(_lock);
// Update the current frame
if (currentFrame) {
SD_LOCK(self.lock);
SD_LOCK(_lock);
// Remove the frame buffer if need
if (self.frameBuffer.count > self.maxBufferCount) {
self.frameBuffer[@(currentFrameIndex)] = nil;
@ -281,7 +275,7 @@
if (self.frameBuffer.count == totalFrameCount) {
bufferFull = YES;
}
SD_UNLOCK(self.lock);
SD_UNLOCK(_lock);
// Update the current frame immediately
self.currentFrame = currentFrame;
@ -342,9 +336,9 @@
// Or, most cases, the decode speed is faster than render speed, we fetch next frame
NSUInteger fetchFrameIndex = self.bufferMiss? currentFrameIndex : nextFrameIndex;
UIImage *fetchFrame;
SD_LOCK(self.lock);
SD_LOCK(_lock);
fetchFrame = self.bufferMiss? nil : self.frameBuffer[@(nextFrameIndex)];
SD_UNLOCK(self.lock);
SD_UNLOCK(_lock);
if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
// Prefetch next frame in background queue
@ -359,9 +353,9 @@
BOOL isAnimating = self.displayLink.isRunning;
if (isAnimating) {
SD_LOCK(self.lock);
SD_LOCK(self->_lock);
self.frameBuffer[@(fetchFrameIndex)] = frame;
SD_UNLOCK(self.lock);
SD_UNLOCK(self->_lock);
}
}];
[self.fetchQueue addOperation:operation];

View File

@ -15,7 +15,7 @@
/**
A drop-in replacement for UIImageView/NSImageView, you can use this for animated image rendering.
Call `setImage:` with `UIImage(NSImage)` which conform to `SDAnimatedImage` protocol will start animated image rendering. Call with normal UIImage(NSImage) will back to normal UIImageView(NSImageView) rendering
Call `setImage:` with `UIImage(NSImage)` which conforms to `SDAnimatedImage` protocol will start animated image rendering. Call with normal UIImage(NSImage) will back to normal UIImageView(NSImageView) rendering
For UIKit: use `-startAnimating`, `-stopAnimating` to control animating. `isAnimating` to check animation state.
For AppKit: use `-setAnimates:` to control animating, `animates` to check animation state. This view is layer-backed.
*/
@ -93,7 +93,7 @@
@property (nonatomic, assign) BOOL resetFrameIndexWhenStopped;
/**
If the image has more than one frame, set this value to `YES` will automatically
If the image which conforms to `SDAnimatedImage` protocol has more than one frame, set this value to `YES` will automatically
play/stop the animation when the view become visible/invisible.
Default is YES.
*/

View File

@ -418,7 +418,8 @@
/// Check if it should be played
- (void)checkPlay
{
if (self.autoPlayAnimatedImage) {
// Only handle for SDAnimatedImage, leave UIAnimatedImage or animationImages for super implementation control
if (self.player && self.autoPlayAnimatedImage) {
[self updateShouldAnimate];
if (self.shouldAnimate) {
[self startAnimating];

View File

@ -341,7 +341,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
* @return The image for the given key, or nil if not found.
*/
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context;;
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context;
#pragma mark - Remove Ops

View File

@ -13,13 +13,12 @@
@interface SDImageCachesManager ()
@property (nonatomic, strong, nonnull) dispatch_semaphore_t cachesLock;
@property (nonatomic, strong, nonnull) NSMutableArray<id<SDImageCache>> *imageCaches;
@end
@implementation SDImageCachesManager
{
NSMutableArray<id<SDImageCache>> *_imageCaches;
@implementation SDImageCachesManager {
SD_LOCK_DECLARE(_cachesLock);
}
+ (SDImageCachesManager *)sharedManager {
@ -41,25 +40,25 @@
self.clearOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent;
// initialize with default image caches
_imageCaches = [NSMutableArray arrayWithObject:[SDImageCache sharedImageCache]];
_cachesLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_cachesLock);
}
return self;
}
- (NSArray<id<SDImageCache>> *)caches {
SD_LOCK(self.cachesLock);
SD_LOCK(_cachesLock);
NSArray<id<SDImageCache>> *caches = [_imageCaches copy];
SD_UNLOCK(self.cachesLock);
SD_UNLOCK(_cachesLock);
return caches;
}
- (void)setCaches:(NSArray<id<SDImageCache>> *)caches {
SD_LOCK(self.cachesLock);
SD_LOCK(_cachesLock);
[_imageCaches removeAllObjects];
if (caches.count) {
[_imageCaches addObjectsFromArray:caches];
}
SD_UNLOCK(self.cachesLock);
SD_UNLOCK(_cachesLock);
}
#pragma mark - Cache IO operations
@ -68,18 +67,18 @@
if (![cache conformsToProtocol:@protocol(SDImageCache)]) {
return;
}
SD_LOCK(self.cachesLock);
SD_LOCK(_cachesLock);
[_imageCaches addObject:cache];
SD_UNLOCK(self.cachesLock);
SD_UNLOCK(_cachesLock);
}
- (void)removeCache:(id<SDImageCache>)cache {
if (![cache conformsToProtocol:@protocol(SDImageCache)]) {
return;
}
SD_LOCK(self.cachesLock);
SD_LOCK(_cachesLock);
[_imageCaches removeObject:cache];
SD_UNLOCK(self.cachesLock);
SD_UNLOCK(_cachesLock);
}
#pragma mark - SDImageCache

View File

@ -93,7 +93,7 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeEmbedThumb
But this may be useful for some custom coders, because some business logic may dependent on things other than image or image data information only.
See `SDWebImageContext` for more detailed information.
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderWebImageContext API_DEPRECATED("The coder component will be seperated from Core subspec in the future. Update your code to not rely on this context option.", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED));;
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderWebImageContext API_DEPRECATED("The coder component will be seperated from Core subspec in the future. Update your code to not rely on this context option.", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED));
#pragma mark - Coder
/**

View File

@ -15,13 +15,12 @@
@interface SDImageCodersManager ()
@property (nonatomic, strong, nonnull) dispatch_semaphore_t codersLock;
@property (nonatomic, strong, nonnull) NSMutableArray<id<SDImageCoder>> *imageCoders;
@end
@implementation SDImageCodersManager
{
NSMutableArray<id<SDImageCoder>> *_imageCoders;
@implementation SDImageCodersManager {
SD_LOCK_DECLARE(_codersLock);
}
+ (nonnull instancetype)sharedManager {
@ -37,27 +36,27 @@
if (self = [super init]) {
// initialize with default coders
_imageCoders = [NSMutableArray arrayWithArray:@[[SDImageIOCoder sharedCoder], [SDImageGIFCoder sharedCoder], [SDImageAPNGCoder sharedCoder]]];
_codersLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_codersLock);
}
return self;
}
- (NSArray<id<SDImageCoder>> *)coders
{
SD_LOCK(self.codersLock);
SD_LOCK(_codersLock);
NSArray<id<SDImageCoder>> *coders = [_imageCoders copy];
SD_UNLOCK(self.codersLock);
SD_UNLOCK(_codersLock);
return coders;
}
- (void)setCoders:(NSArray<id<SDImageCoder>> *)coders
{
SD_LOCK(self.codersLock);
SD_LOCK(_codersLock);
[_imageCoders removeAllObjects];
if (coders.count) {
[_imageCoders addObjectsFromArray:coders];
}
SD_UNLOCK(self.codersLock);
SD_UNLOCK(_codersLock);
}
#pragma mark - Coder IO operations
@ -66,18 +65,18 @@
if (![coder conformsToProtocol:@protocol(SDImageCoder)]) {
return;
}
SD_LOCK(self.codersLock);
SD_LOCK(_codersLock);
[_imageCoders addObject:coder];
SD_UNLOCK(self.codersLock);
SD_UNLOCK(_codersLock);
}
- (void)removeCoder:(nonnull id<SDImageCoder>)coder {
if (![coder conformsToProtocol:@protocol(SDImageCoder)]) {
return;
}
SD_LOCK(self.codersLock);
SD_LOCK(_codersLock);
[_imageCoders removeObject:coder];
SD_UNLOCK(self.codersLock);
SD_UNLOCK(_codersLock);
}
#pragma mark - SDImageCoder

View File

@ -655,7 +655,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
return nil;
}
image.sd_imageFormat = self.class.imageFormat;
image.sd_isDecoded = YES;;
image.sd_isDecoded = YES;
return image;
}

View File

@ -60,6 +60,7 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NS
*/
@protocol SDImageLoader <NSObject>
@required
/**
Whether current image loader supports to load the provide image URL.
This will be checked every time a new image request come for loader. If this return NO, we will mark this image load as failed. If return YES, we will start to call `requestImageWithURL:options:context:progress:completed:`.
@ -67,8 +68,23 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NS
@param url The image URL to be loaded.
@return YES to continue download, NO to stop download.
*/
- (BOOL)canRequestImageForURL:(nullable NSURL *)url;
- (BOOL)canRequestImageForURL:(nullable NSURL *)url API_DEPRECATED("Use canRequestImageForURL:options:context: instead", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED));
@optional
/**
Whether current image loader supports to load the provide image URL, with associated options and context.
This will be checked every time a new image request come for loader. If this return NO, we will mark this image load as failed. If return YES, we will start to call `requestImageWithURL:options:context:progress:completed:`.
@param url The image URL to be loaded.
@param options A mask to specify options to use for this request
@param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
@return YES to continue download, NO to stop download.
*/
- (BOOL)canRequestImageForURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context;
@required
/**
Load the image and image data with the given URL and return the image data. You're responsible for producing the image instance.
@ -96,6 +112,22 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NS
@return Whether to block this url or not. Return YES to mark this URL as failed.
*/
- (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url
error:(nonnull NSError *)error;
error:(nonnull NSError *)error API_DEPRECATED("Use shouldBlockFailedURLWithURL:error:options:context: instead", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED));
@optional
/**
Whether the error from image loader should be marked indeed un-recoverable or not, with associated options and context.
If this return YES, failed URL which does not using `SDWebImageRetryFailed` will be blocked into black list. Else not.
@param url The URL represent the image. Note this may not be a HTTP URL
@param error The URL's loading error, from previous `requestImageWithURL:options:context:progress:completed:` completedBlock's error.
@param options A mask to specify options to use for this request
@param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
@return Whether to block this url or not. Return YES to mark this URL as failed.
*/
- (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url
error:(nonnull NSError *)error
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context;
@end

View File

@ -12,13 +12,12 @@
@interface SDImageLoadersManager ()
@property (nonatomic, strong, nonnull) dispatch_semaphore_t loadersLock;
@property (nonatomic, strong, nonnull) NSMutableArray<id<SDImageLoader>> *imageLoaders;
@end
@implementation SDImageLoadersManager
{
NSMutableArray<id<SDImageLoader>>* _imageLoaders;
@implementation SDImageLoadersManager {
SD_LOCK_DECLARE(_loadersLock);
}
+ (SDImageLoadersManager *)sharedManager {
@ -35,25 +34,25 @@
if (self) {
// initialize with default image loaders
_imageLoaders = [NSMutableArray arrayWithObject:[SDWebImageDownloader sharedDownloader]];
_loadersLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_loadersLock);
}
return self;
}
- (NSArray<id<SDImageLoader>> *)loaders {
SD_LOCK(self.loadersLock);
SD_LOCK(_loadersLock);
NSArray<id<SDImageLoader>>* loaders = [_imageLoaders copy];
SD_UNLOCK(self.loadersLock);
SD_UNLOCK(_loadersLock);
return loaders;
}
- (void)setLoaders:(NSArray<id<SDImageLoader>> *)loaders {
SD_LOCK(self.loadersLock);
SD_LOCK(_loadersLock);
[_imageLoaders removeAllObjects];
if (loaders.count) {
[_imageLoaders addObjectsFromArray:loaders];
}
SD_UNLOCK(self.loadersLock);
SD_UNLOCK(_loadersLock);
}
#pragma mark - Loader Property
@ -62,18 +61,18 @@
if (![loader conformsToProtocol:@protocol(SDImageLoader)]) {
return;
}
SD_LOCK(self.loadersLock);
SD_LOCK(_loadersLock);
[_imageLoaders addObject:loader];
SD_UNLOCK(self.loadersLock);
SD_UNLOCK(_loadersLock);
}
- (void)removeLoader:(id<SDImageLoader>)loader {
if (![loader conformsToProtocol:@protocol(SDImageLoader)]) {
return;
}
SD_LOCK(self.loadersLock);
SD_LOCK(_loadersLock);
[_imageLoaders removeObject:loader];
SD_UNLOCK(self.loadersLock);
SD_UNLOCK(_loadersLock);
}
#pragma mark - SDImageLoader

View File

@ -13,12 +13,15 @@
static void * SDMemoryCacheContext = &SDMemoryCacheContext;
@interface SDMemoryCache <KeyType, ObjectType> ()
@interface SDMemoryCache <KeyType, ObjectType> () {
#if SD_UIKIT
SD_LOCK_DECLARE(_weakCacheLock); // a lock to keep the access to `weakCache` thread-safe
#endif
}
@property (nonatomic, strong, nullable) SDImageCacheConfig *config;
#if SD_UIKIT
@property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; // strong-weak cache
@property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock; // a lock to keep the access to `weakCache` thread-safe
#endif
@end
@ -61,7 +64,7 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
#if SD_UIKIT
self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
self.weakCacheLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_weakCacheLock);
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning:)
@ -85,9 +88,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
}
if (key && obj) {
// Store weak cache
SD_LOCK(self.weakCacheLock);
SD_LOCK(_weakCacheLock);
[self.weakCache setObject:obj forKey:key];
SD_UNLOCK(self.weakCacheLock);
SD_UNLOCK(_weakCacheLock);
}
}
@ -98,9 +101,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
}
if (key && !obj) {
// Check weak cache
SD_LOCK(self.weakCacheLock);
SD_LOCK(_weakCacheLock);
obj = [self.weakCache objectForKey:key];
SD_UNLOCK(self.weakCacheLock);
SD_UNLOCK(_weakCacheLock);
if (obj) {
// Sync cache
NSUInteger cost = 0;
@ -120,9 +123,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
}
if (key) {
// Remove weak cache
SD_LOCK(self.weakCacheLock);
SD_LOCK(_weakCacheLock);
[self.weakCache removeObjectForKey:key];
SD_UNLOCK(self.weakCacheLock);
SD_UNLOCK(_weakCacheLock);
}
}
@ -132,9 +135,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
return;
}
// Manually remove should also remove weak cache
SD_LOCK(self.weakCacheLock);
SD_LOCK(_weakCacheLock);
[self.weakCache removeAllObjects];
SD_UNLOCK(self.weakCacheLock);
SD_UNLOCK(_weakCacheLock);
}
#endif

View File

@ -40,15 +40,16 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, NSOperation<SDWebImageDownloaderOperation> *> *URLOperations;
@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
// The session in which data tasks will run
@property (strong, nonatomic) NSURLSession *session;
@end
@implementation SDWebImageDownloader
@implementation SDWebImageDownloader {
SD_LOCK_DECLARE(_HTTPHeadersLock); // A lock to keep the access to `HTTPHeaders` thread-safe
SD_LOCK_DECLARE(_operationsLock); // A lock to keep the access to `URLOperations` thread-safe
}
+ (void)initialize {
// Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
@ -120,8 +121,8 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
}
headerDictionary[@"Accept"] = @"image/*,*/*;q=0.8";
_HTTPHeaders = headerDictionary;
_HTTPHeadersLock = dispatch_semaphore_create(1);
_operationsLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_HTTPHeadersLock);
SD_LOCK_INIT(_operationsLock);
NSURLSessionConfiguration *sessionConfiguration = _config.sessionConfiguration;
if (!sessionConfiguration) {
sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
@ -161,18 +162,18 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
if (!field) {
return;
}
SD_LOCK(self.HTTPHeadersLock);
SD_LOCK(_HTTPHeadersLock);
[self.HTTPHeaders setValue:value forKey:field];
SD_UNLOCK(self.HTTPHeadersLock);
SD_UNLOCK(_HTTPHeadersLock);
}
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
if (!field) {
return nil;
}
SD_LOCK(self.HTTPHeadersLock);
SD_LOCK(_HTTPHeadersLock);
NSString *value = [self.HTTPHeaders objectForKey:field];
SD_UNLOCK(self.HTTPHeadersLock);
SD_UNLOCK(_HTTPHeadersLock);
return value;
}
@ -202,14 +203,14 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
return nil;
}
SD_LOCK(self.operationsLock);
SD_LOCK(_operationsLock);
id downloadOperationCancelToken;
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
// 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) {
operation = [self createDownloaderOperationWithUrl:url options:options context:context];
if (!operation) {
SD_UNLOCK(self.operationsLock);
SD_UNLOCK(_operationsLock);
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
completedBlock(nil, nil, error, YES);
@ -222,9 +223,9 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
if (!self) {
return;
}
SD_LOCK(self.operationsLock);
SD_LOCK(self->_operationsLock);
[self.URLOperations removeObjectForKey:url];
SD_UNLOCK(self.operationsLock);
SD_UNLOCK(self->_operationsLock);
};
self.URLOperations[url] = operation;
// Add the handlers before submitting to operation queue, avoid the race condition that operation finished before setting handlers.
@ -248,7 +249,7 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
}
}
}
SD_UNLOCK(self.operationsLock);
SD_UNLOCK(_operationsLock);
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url;
@ -271,9 +272,9 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
mutableRequest.HTTPShouldHandleCookies = SD_OPTIONS_CONTAINS(options, SDWebImageDownloaderHandleCookies);
mutableRequest.HTTPShouldUsePipelining = YES;
SD_LOCK(self.HTTPHeadersLock);
SD_LOCK(_HTTPHeadersLock);
mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
SD_UNLOCK(self.HTTPHeadersLock);
SD_UNLOCK(_HTTPHeadersLock);
// Context Option
SDWebImageMutableContext *mutableContext;
@ -561,6 +562,10 @@ didReceiveResponse:(NSURLResponse *)response
@implementation SDWebImageDownloader (SDImageLoader)
- (BOOL)canRequestImageForURL:(NSURL *)url {
return [self canRequestImageForURL:url options:0 context:nil];
}
- (BOOL)canRequestImageForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
if (!url) {
return NO;
}
@ -596,6 +601,10 @@ didReceiveResponse:(NSURLResponse *)response
}
- (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error {
return [self shouldBlockFailedURLWithURL:url error:error options:0 context:nil];
}
- (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
BOOL shouldBlockFailedURL;
// Filter the error domain and check error codes
if ([error.domain isEqualToString:SDWebImageErrorDomain]) {

View File

@ -26,14 +26,15 @@ static id<SDImageLoader> _defaultImageLoader;
@end
@interface SDWebImageManager ()
@interface SDWebImageManager () {
SD_LOCK_DECLARE(_failedURLsLock); // a lock to keep the access to `failedURLs` thread-safe
SD_LOCK_DECLARE(_runningOperationsLock); // a lock to keep the access to `runningOperations` thread-safe
}
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
@property (strong, nonatomic, readwrite, nonnull) id<SDImageLoader> imageLoader;
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
@property (strong, nonatomic, nonnull) dispatch_semaphore_t failedURLsLock; // a lock to keep the access to `failedURLs` thread-safe
@property (strong, nonatomic, nonnull) NSMutableSet<SDWebImageCombinedOperation *> *runningOperations;
@property (strong, nonatomic, nonnull) dispatch_semaphore_t runningOperationsLock; // a lock to keep the access to `runningOperations` thread-safe
@end
@ -87,9 +88,9 @@ static id<SDImageLoader> _defaultImageLoader;
_imageCache = cache;
_imageLoader = loader;
_failedURLs = [NSMutableSet new];
_failedURLsLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_failedURLsLock);
_runningOperations = [NSMutableSet new];
_runningOperationsLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_runningOperationsLock);
}
return self;
}
@ -188,9 +189,9 @@ static id<SDImageLoader> _defaultImageLoader;
BOOL isFailedUrl = NO;
if (url) {
SD_LOCK(self.failedURLsLock);
SD_LOCK(_failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url];
SD_UNLOCK(self.failedURLsLock);
SD_UNLOCK(_failedURLsLock);
}
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
@ -200,9 +201,9 @@ static id<SDImageLoader> _defaultImageLoader;
return operation;
}
SD_LOCK(self.runningOperationsLock);
SD_LOCK(_runningOperationsLock);
[self.runningOperations addObject:operation];
SD_UNLOCK(self.runningOperationsLock);
SD_UNLOCK(_runningOperationsLock);
// Preprocess the options and context arg to decide the final the result for manager
SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
@ -214,17 +215,17 @@ static id<SDImageLoader> _defaultImageLoader;
}
- (void)cancelAll {
SD_LOCK(self.runningOperationsLock);
SD_LOCK(_runningOperationsLock);
NSSet<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy];
SD_UNLOCK(self.runningOperationsLock);
SD_UNLOCK(_runningOperationsLock);
[copiedOperations makeObjectsPerformSelector:@selector(cancel)]; // This will call `safelyRemoveOperationFromRunning:` and remove from the array
}
- (BOOL)isRunning {
BOOL isRunning = NO;
SD_LOCK(self.runningOperationsLock);
SD_LOCK(_runningOperationsLock);
isRunning = (self.runningOperations.count > 0);
SD_UNLOCK(self.runningOperationsLock);
SD_UNLOCK(_runningOperationsLock);
return isRunning;
}
@ -232,15 +233,15 @@ static id<SDImageLoader> _defaultImageLoader;
if (!url) {
return;
}
SD_LOCK(self.failedURLsLock);
SD_LOCK(_failedURLsLock);
[self.failedURLs removeObject:url];
SD_UNLOCK(self.failedURLsLock);
SD_UNLOCK(_failedURLsLock);
}
- (void)removeAllFailedURLs {
SD_LOCK(self.failedURLsLock);
SD_LOCK(_failedURLsLock);
[self.failedURLs removeAllObjects];
SD_UNLOCK(self.failedURLsLock);
SD_UNLOCK(_failedURLsLock);
}
#pragma mark - Private
@ -378,7 +379,11 @@ static id<SDImageLoader> _defaultImageLoader;
BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
shouldDownload &= [imageLoader canRequestImageForURL:url];
if ([imageLoader respondsToSelector:@selector(canRequestImageForURL:options:context:)]) {
shouldDownload &= [imageLoader canRequestImageForURL:url options:options context:context];
} else {
shouldDownload &= [imageLoader canRequestImageForURL:url];
}
if (shouldDownload) {
if (cachedImage && options & SDWebImageRefreshCached) {
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
@ -411,15 +416,15 @@ static id<SDImageLoader> _defaultImageLoader;
BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context];
if (shouldBlockFailedURL) {
SD_LOCK(self.failedURLsLock);
SD_LOCK(self->_failedURLsLock);
[self.failedURLs addObject:url];
SD_UNLOCK(self.failedURLsLock);
SD_UNLOCK(self->_failedURLsLock);
}
} else {
if ((options & SDWebImageRetryFailed)) {
SD_LOCK(self.failedURLsLock);
SD_LOCK(self->_failedURLsLock);
[self.failedURLs removeObject:url];
SD_UNLOCK(self.failedURLsLock);
SD_UNLOCK(self->_failedURLsLock);
}
// Continue store cache process
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
@ -560,9 +565,9 @@ static id<SDImageLoader> _defaultImageLoader;
if (!operation) {
return;
}
SD_LOCK(self.runningOperationsLock);
SD_LOCK(_runningOperationsLock);
[self.runningOperations removeObject:operation];
SD_UNLOCK(self.runningOperationsLock);
SD_UNLOCK(_runningOperationsLock);
}
- (void)storeImage:(nullable UIImage *)image
@ -631,7 +636,11 @@ static id<SDImageLoader> _defaultImageLoader;
if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) {
shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error];
} else {
shouldBlockFailedURL = [imageLoader shouldBlockFailedURLWithURL:url error:error];
if ([imageLoader respondsToSelector:@selector(shouldBlockFailedURLWithURL:error:options:context:)]) {
shouldBlockFailedURL = [imageLoader shouldBlockFailedURLWithURL:url error:error options:options context:context];
} else {
shouldBlockFailedURL = [imageLoader shouldBlockFailedURLWithURL:url error:error];
}
}
return shouldBlockFailedURL;

View File

@ -22,8 +22,8 @@
unsigned long _totalCount;
// Used to ensure NSPointerArray thread safe
dispatch_semaphore_t _prefetchOperationsLock;
dispatch_semaphore_t _loadOperationsLock;
SD_LOCK_DECLARE(_prefetchOperationsLock)
SD_LOCK_DECLARE(_loadOperationsLock);
}
@property (nonatomic, copy, readwrite) NSArray<NSURL *> *urls;
@ -268,8 +268,8 @@
- (instancetype)init {
self = [super init];
if (self) {
_prefetchOperationsLock = dispatch_semaphore_create(1);
_loadOperationsLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_prefetchOperationsLock);
SD_LOCK_INIT(_loadOperationsLock);
}
return self;
}

View File

@ -32,7 +32,7 @@ static NSArray *SDBundlePreferredScales() {
}
@implementation SDImageAssetManager {
dispatch_semaphore_t _lock;
SD_LOCK_DECLARE(_lock);
}
+ (instancetype)sharedAssetManager {
@ -56,7 +56,7 @@ static NSArray *SDBundlePreferredScales() {
valueOptions = NSPointerFunctionsStrongMemory;
#endif
_imageTable = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsCopyIn valueOptions:valueOptions];
_lock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_lock);
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif

View File

@ -9,9 +9,8 @@
#import "SDImageCachesManagerOperation.h"
#import "SDInternalMacros.h"
@implementation SDImageCachesManagerOperation
{
dispatch_semaphore_t _pendingCountLock;
@implementation SDImageCachesManagerOperation {
SD_LOCK_DECLARE(_pendingCountLock);
}
@synthesize executing = _executing;
@ -21,7 +20,7 @@
- (instancetype)init {
if (self = [super init]) {
_pendingCountLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_pendingCountLock);
_pendingCount = 0;
}
return self;

View File

@ -7,14 +7,44 @@
*/
#import <Foundation/Foundation.h>
#import <os/lock.h>
#import <libkern/OSAtomic.h>
#import "SDmetamacros.h"
#ifndef SD_LOCK_DECLARE
#if TARGET_OS_MACCATALYST
#define SD_LOCK_DECLARE(lock) os_unfair_lock lock;
#else
#define SD_LOCK_DECLARE(lock) os_unfair_lock lock API_AVAILABLE(ios(10.0), tvos(10), watchos(3), macos(10.12)); \
OSSpinLock lock##_deprecated;
#endif
#endif
#ifndef SD_LOCK_INIT
#if TARGET_OS_MACCATALYST
#define SD_LOCK_INIT(lock) lock = OS_UNFAIR_LOCK_INIT;
#else
#define SD_LOCK_INIT(lock) if (@available(iOS 10, tvOS 10, watchOS 3, macOS 10.12, *)) lock = OS_UNFAIR_LOCK_INIT; \
else lock##_deprecated = OS_SPINLOCK_INIT;
#endif
#endif
#ifndef SD_LOCK
#define SD_LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#if TARGET_OS_MACCATALYST
#define SD_LOCK(lock) os_unfair_lock_lock(&lock);
#else
#define SD_LOCK(lock) if (@available(iOS 10, tvOS 10, watchOS 3, macOS 10.12, *)) os_unfair_lock_lock(&lock); \
else OSSpinLockLock(&lock##_deprecated);
#endif
#endif
#ifndef SD_UNLOCK
#define SD_UNLOCK(lock) dispatch_semaphore_signal(lock);
#if TARGET_OS_MACCATALYST
#define SD_UNLOCK(lock) os_unfair_lock_unlock(&lock);
#else
#define SD_UNLOCK(lock) if (@available(iOS 10, tvOS 10, watchOS 3, macOS 10.12, *)) os_unfair_lock_unlock(&lock); \
else OSSpinLockUnlock(&lock##_deprecated);
#endif
#endif
#ifndef SD_OPTIONS_CONTAINS

View File

@ -602,7 +602,27 @@ static BOOL _isCalled;
expect(SDImageAPNGTestCoder.isCalled).equal(YES);
}
- (void)test30AnimatedImagePlaybackModeReverse {
#if SD_UIKIT
- (void)test31AnimatedImageViewSetAnimationImages {
SDAnimatedImageView *imageView = [SDAnimatedImageView new];
UIImage *image = [[UIImage alloc] initWithData:[self testJPEGData]];
imageView.animationImages = @[image];
expect(imageView.animationImages).notTo.beNil();
}
- (void)test32AnimatedImageViewNotStopPlayingAnimationImagesWhenHidden {
SDAnimatedImageView *imageView = [SDAnimatedImageView new];
[self.window addSubview:imageView];
UIImage *image = [[UIImage alloc] initWithData:[self testJPEGData]];
imageView.animationImages = @[image];
[imageView startAnimating];
expect(imageView.animating).beTruthy();
imageView.hidden = YES;
expect(imageView.animating).beTruthy();
}
#endif
- (void)test33AnimatedImagePlaybackModeReverse {
XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView playback reverse mode"];
SDAnimatedImageView *imageView = [SDAnimatedImageView new];
@ -636,7 +656,7 @@ static BOOL _isCalled;
[self waitForExpectationsWithCommonTimeout];
}
- (void)test31AnimatedImagePlaybackModeBounce {
- (void)test34AnimatedImagePlaybackModeBounce {
XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView playback bounce mode"];
SDAnimatedImageView *imageView = [SDAnimatedImageView new];
@ -687,7 +707,7 @@ static BOOL _isCalled;
}];
}
- (void)test32AnimatedImagePlaybackModeReversedBounce{
- (void)test35AnimatedImagePlaybackModeReversedBounce{
XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView playback reverse bounce mode"];
SDAnimatedImageView *imageView = [SDAnimatedImageView new];
@ -737,8 +757,6 @@ static BOOL _isCalled;
}];
}
#pragma mark - Helper
- (UIWindow *)window {
if (!_window) {

View File

@ -26,6 +26,10 @@
}
- (BOOL)canRequestImageForURL:(NSURL *)url {
return [self canRequestImageForURL:url options:0 context:nil];
}
- (BOOL)canRequestImageForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
return YES;
}
@ -60,6 +64,10 @@
}
- (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error {
return [self shouldBlockFailedURLWithURL:url error:error options:0 context:nil];
}
- (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
return NO;
}

View File

@ -15,11 +15,11 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>5.9.4</string>
<string>5.9.5</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>5.9.4</string>
<string>5.9.5</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>