瀏覽代碼

Merge branch 'master' into gif_bk

Insomnia 5 年之前
父節點
當前提交
c69e80286b

+ 6 - 0
CHANGELOG.md

@@ -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)
 

+ 3 - 0
README.md

@@ -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)
 

+ 1 - 1
SDWebImage.podspec

@@ -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'

+ 16 - 22
SDWebImage/Core/SDAnimatedImagePlayer.m

@@ -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];

+ 2 - 2
SDWebImage/Core/SDAnimatedImageView.h

@@ -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.
  */

+ 2 - 1
SDWebImage/Core/SDAnimatedImageView.m

@@ -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];

+ 1 - 1
SDWebImage/Core/SDImageCache.h

@@ -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
 

+ 12 - 13
SDWebImage/Core/SDImageCachesManager.m

@@ -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

+ 1 - 1
SDWebImage/Core/SDImageCoder.h

@@ -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
 /**

+ 12 - 13
SDWebImage/Core/SDImageCodersManager.m

@@ -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

+ 1 - 1
SDWebImage/Core/SDImageIOAnimatedCoder.m

@@ -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;
 }
 

+ 34 - 2
SDWebImage/Core/SDImageLoader.h

@@ -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

+ 12 - 13
SDWebImage/Core/SDImageLoadersManager.m

@@ -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

+ 14 - 11
SDWebImage/Core/SDMemoryCache.m

@@ -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
 

+ 25 - 16
SDWebImage/Core/SDWebImageDownloader.m

@@ -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]) {

+ 34 - 25
SDWebImage/Core/SDWebImageManager.m

@@ -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;

+ 4 - 4
SDWebImage/Core/SDWebImagePrefetcher.m

@@ -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;
 }

+ 2 - 2
SDWebImage/Private/SDImageAssetManager.m

@@ -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

+ 3 - 4
SDWebImage/Private/SDImageCachesManagerOperation.m

@@ -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;

+ 32 - 2
SDWebImage/Private/SDInternalMacros.h

@@ -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

+ 23 - 5
Tests/Tests/SDAnimatedImageTest.m

@@ -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) {

+ 8 - 0
Tests/Tests/SDWebImageTestLoader.m

@@ -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;
 }
 

+ 2 - 2
WebImage/Info.plist

@@ -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>