Merge branch 'fix-ImageIO-encode/decode-crash' of https://github.com/kinarobin/SDWebImage into fix-ImageIO-encode/decode-crash

This commit is contained in:
kinarobin 2021-01-02 18:12:30 +08:00
commit 872b59e32a
38 changed files with 707 additions and 339 deletions

View File

@ -34,7 +34,7 @@ Doing this helps prioritize the most common problems and requests.
When reporting issues, please include the following:
- The platform name and version (e.g. iOS 8.1)
- The platform name and version (e.g. iOS 10.0)
- The library version
- The integration method (e.g. CocoaPods/Carthage/manually)
- The version of Xcode you're using

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
open_collective: SDWebImage

View File

@ -1,3 +1,46 @@
## [5.10.2 - 5.10 Patch, on Dec 29th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.10.2)
See [all tickets marked for the 5.10.2 release](https://github.com/SDWebImage/SDWebImage/milestone/85)
### Fixes
- Fix the case that we setFinished=YES before NSOperation started. This may cause exception from Foundation #3146
## [5.10.1 - 5.10 Patch, on Dec 25th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.10.1)
See [all tickets marked for the 5.10.1 release](https://github.com/SDWebImage/SDWebImage/milestone/83)
### Fixes
- Fix the race condition when user cancel the network loading will not trigger the completion block #3142
- Fix deprecation warnings for OSSpinLock #3137
## [5.10.0 - iOS 9+/Xcode 11+, on Nov 22nd, 2020](https://github.com/rs/SDWebImage/releases/tag/5.10.0)
See [all tickets marked for the 5.10.0 release](https://github.com/SDWebImage/SDWebImage/milestone/82)
### Project
- Bumped the min deployment target version to iOS 9, macOS 10.11. Bumped the min Xcode version to Xcode 11 #3130
- This effect the downstream dependencies like [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder), you may update them to the latest version at the same time.
### Features
#### Animated Image
- Add animation playback mode for SDAnimatedImageView/Player, including reverse, bounce and reversed bounce #3115
#### Custom Loader
- Added the options and context arg for Image Loader custom protocol, this can be used for advanced user to grab and check for context for current loading URL to process the logic #3129
#### Performance
- Replace all current dispatch_semaphore usage into the os_unfair_lock, use OSSpinLock for lower firmware #3128
## [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)
### Fixes
- Fix race condition when using transitions that are canceled and then switched to a new transition or load operation #3108 #3107
- Fixed the store cache type was specified to `SDImageCacheTypeDisk ` that no pictures were obtained when the disk had pictures #3110
## [5.9.3 - 5.9 Patch, on Oct 13th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.9.3)
See [all tickets marked for the 5.9.3 release](https://github.com/SDWebImage/SDWebImage/milestone/79)

View File

@ -176,7 +176,7 @@ GCC_WARN_UNUSED_VARIABLE = YES
HEADER_SEARCH_PATHS = $(inherited)
// Code will load on this and later versions of iOS. Framework APIs that are unavailable in earlier versions will be weak-linked; your code should check for null function pointers or specific system versions before calling newer APIs.
IPHONEOS_DEPLOYMENT_TARGET = 8.0
IPHONEOS_DEPLOYMENT_TARGET = 9.0
// This is a list of paths to be added to the `runpath` search path list for the image being created. At runtime, `dyld` uses the `runpath` when searching for dylibs whose load path begins with `@rpath/`.
LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks
@ -185,7 +185,7 @@ LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/
LIBRARY_SEARCH_PATHS = $(inherited)
// Code will load on this and later versions of macOS. Framework APIs that are unavailable in earlier versions will be weak-linked; your code should check for null function pointers or specific system versions before calling newer APIs.
MACOSX_DEPLOYMENT_TARGET = 10.10
MACOSX_DEPLOYMENT_TARGET = 10.11
// Options defined in this setting are passed to invocations of the linker.
OTHER_LDFLAGS = -ObjC

View File

@ -57,7 +57,7 @@ Note: neither your success nor failure block will be call if your image request
The `SDWebImageManager` is the class behind the `UIImageView(WebCache)` category. It ties the asynchronous downloader with the image cache store. You can use this class directly to benefit from web image downloading with caching in another context than a `UIView` (ie: with Cocoa).
Note: When the image is from memory cache, it will not contains any `NSData` by default. However, if you need image data, you can pass `SDWebImageQueryDataWhenInMemory` in options arg.
Note: When the image is from memory cache, it will not contain any `NSData` by default. However, if you need image data, you can pass `SDWebImageQueryDataWhenInMemory` in options arg.
Here is a simple example of how to use `SDWebImageManager`:
@ -95,11 +95,11 @@ SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
### Using Asynchronous Image Caching Independently
It is also possible to use the async based image cache store independently. SDImageCache
It is also possible to use the async based image cache store independently. `SDImageCache`
maintains a memory cache and an optional disk cache. Disk cache write operations are performed
asynchronous so it doesn't add unnecessary latency to the UI.
The SDImageCache class provides a singleton instance for convenience but you can create your own
The `SDImageCache` class provides a singleton instance for convenience but you can create your own
instance if you want to create separated cache namespace.
To lookup the cache, you use the `queryDiskCacheForKey:done:` method. If the method returns nil, it means the cache
@ -114,10 +114,10 @@ SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace
}];
```
By default SDImageCache will lookup the disk cache if an image can't be found in the memory cache.
By default `SDImageCache` will lookup the disk cache if an image can't be found in the memory cache.
You can prevent this from happening by calling the alternative method `imageFromMemoryCacheForKey:`.
To store an image into the cache, you use the storeImage:forKey:completion: method:
To store an image into the cache, you use the `storeImage:forKey:completion:` method:
```objective-c
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey completion:^{
@ -126,14 +126,14 @@ To store an image into the cache, you use the storeImage:forKey:completion: meth
```
By default, the image will be stored in memory cache as well as on disk cache (asynchronously). If
you want only the memory cache, use the alternative method storeImage:forKey:toDisk:completion: with a negative
you want only the memory cache, use the alternative method `storeImage:forKey:toDisk:completion:` with a negative
third argument.
### Using cache key filter
Sometime, you may not want to use the image URL as cache key because part of the URL is dynamic
(i.e.: for access control purpose). SDWebImageManager provides a way to set a cache key filter that
takes the NSURL as input, and output a cache key NSString.
(i.e.: for access control purpose). `SDWebImageManager` provides a way to set a cache key filter that
takes the `NSURL` as input, and output a cache key `NSString`.
The following example sets a filter in the application delegate that will remove any query-string from
the URL before to use it as a cache key:

View File

@ -4,10 +4,6 @@
#import <Availability.h>
#ifndef __IPHONE_4_0
#warning "This project uses features only available in iOS SDK 4.0 and later."
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>

View File

@ -6,8 +6,8 @@ import PackageDescription
let package = Package(
name: "SDWebImage",
platforms: [
.macOS(.v10_10),
.iOS(.v8),
.macOS(.v10_11),
.iOS(.v9),
.tvOS(.v9),
.watchOS(.v2)
],

12
Podfile
View File

@ -23,19 +23,19 @@ workspace 'SDWebImage.xcworkspace'
# Example Project
target 'SDWebImage iOS Demo' do
project example_project_path
platform :ios, '8.0'
platform :ios, '9.0'
all_example_pods
end
target 'SDWebImage OSX Demo' do
project example_project_path
platform :osx, '10.10'
platform :osx, '10.11'
all_example_pods
end
target 'SDWebImage TV Demo' do
project example_project_path
platform :tvos, '9.2'
platform :tvos, '9.0'
all_example_pods
end
@ -48,18 +48,18 @@ end
# Test Project
target 'Tests iOS' do
project test_project_path
platform :ios, '8.0'
platform :ios, '9.0'
all_test_pods
end
target 'Tests Mac' do
project test_project_path
platform :osx, '10.10'
platform :osx, '10.11'
all_test_pods
end
target 'Tests TV' do
project test_project_path
platform :tvos, '9.2'
platform :tvos, '9.0'
all_test_pods
end

View File

@ -37,8 +37,9 @@ This library provides an async image downloader with cache support. For convenie
## Supported Image Formats
- Image formats supported by Apple system (JPEG, PNG, TIFF, HEIC, ...), including GIF/APNG/HEIC animation
- WebP format, including animated WebP (use the [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder) project). Note iOS 14/macOS 11.0 supports built-in WebP decoding (no encoding).
- Image formats supported by Apple system (JPEG, PNG, TIFF, BMP, ...), including [GIF](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#gif-coder)/[APNG](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#apng-coder) animated image
- HEIC format from iOS 11/macOS 10.13, including animated HEIC from iOS 13/macOS 10.15 via [SDWebImageHEICCoder](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#heic-coder). For lower firmware, use coder plugin [SDWebImageHEIFCoder](https://github.com/SDWebImage/SDWebImageHEIFCoder)
- WebP format from iOS 14/macOS 11.0 via [SDWebImageAWebPCoder](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#awebp-coder). For lower firmware, use coder plugin [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder)
- Support extendable coder plugins for new image formats like BPG, AVIF. And vector format like PDF, SVG. See all the list in [Image coder plugin List](https://github.com/SDWebImage/SDWebImage/wiki/Coder-Plugin-List)
## Additional modules and Ecosystem
@ -93,14 +94,15 @@ You can use those directly, or create similar components of your own, by using t
## Requirements
- iOS 8.0 or later
- iOS 9.0 or later
- tvOS 9.0 or later
- watchOS 2.0 or later
- macOS 10.10 or later (10.15 for Catalyst)
- Xcode 10.0 or later
- macOS 10.11 or later (10.15 for Catalyst)
- Xcode 11.0 or later
#### Backwards compatibility
- For iOS 8, macOS 10.10 or Xcode < 11, use [any 5.x version up to 5.9.5](https://github.com/SDWebImage/SDWebImage/releases/tag/5.9.5)
- For iOS 7, macOS 10.9 or Xcode < 8, use [any 4.x version up to 4.4.6](https://github.com/SDWebImage/SDWebImage/releases/tag/4.4.6)
- For macOS 10.8, use [any 4.x version up to 4.3.0](https://github.com/SDWebImage/SDWebImage/releases/tag/4.3.0)
- For iOS 5 and 6, use [any 3.x version up to 3.7.6](https://github.com/SDWebImage/SDWebImage/tag/3.7.6)
@ -164,7 +166,7 @@ This animated image solution is available for `iOS`/`tvOS`/`macOS`. The `SDAnima
The `SDAnimatedImageView` supports the familiar image loading category methods, works like drop-in replacement for `UIImageView/NSImageView`.
Don't have UIView (like WatchKit or CALayer)? you can still use `SDAnimatedPlayer` the player engine for advanced playback and rendering.
Don't have `UIView` (like `WatchKit` or `CALayer`)? you can still use `SDAnimatedPlayer` the player engine for advanced playback and rendering.
See [Animated Image](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50) for more detailed information.
@ -207,9 +209,9 @@ pod 'SDWebImage', '~> 5.0'
##### Swift and static framework
Swift project previously have to use `use_frameworks!` to make all Pods into dynamic framework to let CocoaPods works.
Swift project previously had to use `use_frameworks!` to make all Pods into dynamic framework to let CocoaPods work.
However, start with `CocoaPods 1.5.0+` (with `Xcode 9+`), which supports to build both Objective-C && Swift code into static framework. You can use modular headers to use SDWebImage as static framework, without the need of `use_frameworks!`:
However, starting with `CocoaPods 1.5.0+` (with `Xcode 9+`), which supports to build both Objective-C && Swift code into static framework. You can use modular headers to use SDWebImage as static framework, without the need of `use_frameworks!`:
```
platform :ios, '8.0'
@ -238,7 +240,7 @@ Podfile example:
pod 'SDWebImage/MapKit'
```
### Installation with Carthage (iOS 8+)
### Installation with Carthage
[Carthage](https://github.com/Carthage/Carthage) is a lightweight dependency manager for Swift and Objective-C. It leverages CocoaTouch modules and is less invasive than CocoaPods.
@ -295,6 +297,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,9 +1,9 @@
Pod::Spec.new do |s|
s.name = 'SDWebImage'
s.version = '5.9.3'
s.version = '5.10.2'
s.osx.deployment_target = '10.10'
s.ios.deployment_target = '8.0'
s.osx.deployment_target = '10.11'
s.ios.deployment_target = '9.0'
s.tvos.deployment_target = '9.0'
s.watchos.deployment_target = '2.0'
@ -37,9 +37,9 @@ Pod::Spec.new do |s|
end
s.subspec 'MapKit' do |mk|
mk.osx.deployment_target = '10.10'
mk.ios.deployment_target = '8.0'
mk.tvos.deployment_target = '9.2'
mk.osx.deployment_target = '10.11'
mk.ios.deployment_target = '9.0'
mk.tvos.deployment_target = '9.0'
mk.source_files = 'SDWebImageMapKit/MapKit/*.{h,m}'
mk.framework = 'MapKit'
mk.dependency 'SDWebImage/Core'

View File

@ -10,6 +10,25 @@
#import "SDWebImageCompat.h"
#import "SDImageCoder.h"
typedef NS_ENUM(NSUInteger, SDAnimatedImagePlaybackMode) {
/**
* From first to last frame and stop or next loop.
*/
SDAnimatedImagePlaybackModeNormal = 0,
/**
* From last frame to first frame and stop or next loop.
*/
SDAnimatedImagePlaybackModeReverse,
/**
* From first frame to last frame and reverse again, like reciprocating.
*/
SDAnimatedImagePlaybackModeBounce,
/**
* From last frame to first frame and reverse again, like reversed reciprocating.
*/
SDAnimatedImagePlaybackModeReversedBounce,
};
/// A player to control the playback of animated image, which can be used to drive Animated ImageView or any rendering usage, like CALayer/WatchKit/SwiftUI rendering.
@interface SDAnimatedImagePlayer : NSObject
@ -37,6 +56,9 @@
/// `< 0.0` is not supported currently and stop animation. (may support reverse playback in the future)
@property (nonatomic, assign) double playbackRate;
/// Asynchronous setup animation playback mode. Default mode is SDAnimatedImagePlaybackModeNormal.
@property (nonatomic, assign) SDAnimatedImagePlaybackMode playbackMode;
/// Provide a max buffer size by bytes. This is used to adjust frame buffer count and can be useful when the decoding cost is expensive (such as Animated WebP software decoding). Default is 0.
/// `0` means automatically adjust by calculating current memory usage.
/// `1` means without any buffer cache, each of frames will be decoded and then be freed after rendering. (Lowest Memory and Highest CPU)

View File

@ -13,6 +13,7 @@
#import "SDInternalMacros.h"
@interface SDAnimatedImagePlayer () {
SD_LOCK_DECLARE(_lock);
NSRunLoopMode _runLoopMode;
}
@ -24,9 +25,9 @@
@property (nonatomic, assign) NSTimeInterval currentTime;
@property (nonatomic, assign) BOOL bufferMiss;
@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
@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
@ -46,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
@ -70,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) {
@ -78,7 +80,7 @@
[self.frameBuffer removeObjectForKey:key];
}
}
SD_UNLOCK(self.lock);
SD_UNLOCK(self->_lock);
}];
}
@ -98,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:)];
@ -142,7 +137,12 @@
if (self.currentFrameIndex != 0) {
return;
}
if ([self.animatedProvider isKindOfClass:[UIImage class]]) {
if (self.playbackMode == SDAnimatedImagePlaybackModeReverse ||
self.playbackMode == SDAnimatedImagePlaybackModeReversedBounce) {
self.currentFrameIndex = self.totalFrameCount - 1;
}
if (!self.currentFrame && [self.animatedProvider isKindOfClass:[UIImage class]]) {
UIImage *image = (UIImage *)self.animatedProvider;
// Use the poster image if available
#if SD_MAC
@ -152,12 +152,13 @@
#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];
}
}
}
- (void)resetCurrentFrameStatus {
@ -171,18 +172,16 @@
}
- (void)clearFrameBuffer {
SD_LOCK(self.lock);
SD_LOCK(_lock);
[_frameBuffer removeAllObjects];
SD_UNLOCK(self.lock);
SD_UNLOCK(_lock);
}
#pragma mark - Animation Control
- (void)startPlaying {
[self.displayLink start];
// Setup frame
if (self.currentFrameIndex == 0 && !self.currentFrame) {
[self setupCurrentFrame];
}
[self setupCurrentFrame];
// Calculate max buffer size
[self calculateMaxBufferCount];
}
@ -242,17 +241,32 @@
NSUInteger currentFrameIndex = self.currentFrameIndex;
NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
if (self.playbackMode == SDAnimatedImagePlaybackModeReverse) {
nextFrameIndex = currentFrameIndex == 0 ? (totalFrameCount - 1) : (currentFrameIndex - 1) % totalFrameCount;
} else if (self.playbackMode == SDAnimatedImagePlaybackModeBounce ||
self.playbackMode == SDAnimatedImagePlaybackModeReversedBounce) {
if (currentFrameIndex == 0) {
self.shouldReverse = false;
} else if (currentFrameIndex == totalFrameCount - 1) {
self.shouldReverse = true;
}
nextFrameIndex = self.shouldReverse ? (currentFrameIndex - 1) : (currentFrameIndex + 1);
nextFrameIndex %= totalFrameCount;
}
// Check if we need to display new frame firstly
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;
@ -261,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;
@ -322,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
@ -339,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

@ -11,14 +11,21 @@
#if SD_UIKIT || SD_MAC
#import "SDAnimatedImage.h"
#import "SDAnimatedImagePlayer.h"
/**
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.
*/
@interface SDAnimatedImageView : UIImageView
/**
The internal animation player.
This property is only used for advanced usage, like inspecting/debugging animation status, control progressive loading, complicated animation frame index control, etc.
@warning Pay attention if you directly update the player's property like `totalFrameCount`, `totalLoopCount`, the same property on `SDAnimatedImageView` may not get synced.
*/
@property (nonatomic, strong, readonly, nullable) SDAnimatedImagePlayer *player;
/**
Current display frame image. This value is KVO Compliance.
@ -52,6 +59,10 @@
`< 0.0` is not supported currently and stop animation. (may support reverse playback in the future)
*/
@property (nonatomic, assign) double playbackRate;
/// Asynchronous setup animation playback mode. Default mode is SDAnimatedImagePlaybackModeNormal.
@property (nonatomic, assign) SDAnimatedImagePlaybackMode playbackMode;
/**
Provide a max buffer size by bytes. This is used to adjust frame buffer count and can be useful when the decoding cost is expensive (such as Animated WebP software decoding). Default is 0.
`0` means automatically adjust by calculating current memory usage.
@ -82,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

@ -10,7 +10,6 @@
#if SD_UIKIT || SD_MAC
#import "SDAnimatedImagePlayer.h"
#import "UIImage+Metadata.h"
#import "NSImage+Compatibility.h"
#import "SDInternalMacros.h"
@ -24,14 +23,15 @@
NSRunLoopMode _runLoopMode;
NSUInteger _maxBufferSize;
double _playbackRate;
SDAnimatedImagePlaybackMode _playbackMode;
}
@property (nonatomic, strong, readwrite) SDAnimatedImagePlayer *player;
@property (nonatomic, strong, readwrite) UIImage *currentFrame;
@property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
@property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
@property (nonatomic, assign) BOOL shouldAnimate;
@property (nonatomic, assign) BOOL isProgressive;
@property (nonatomic,strong) SDAnimatedImagePlayer *player; // The animation player.
@property (nonatomic) CALayer *imageViewLayer; // The actual rendering layer.
@end
@ -164,6 +164,9 @@
// Play Rate
self.player.playbackRate = self.playbackRate;
// Play Mode
self.player.playbackMode = self.playbackMode;
// Setup handler
@weakify(self);
self.player.animationFrameHandler = ^(NSUInteger index, UIImage * frame) {
@ -239,6 +242,19 @@
return _playbackRate;
}
- (void)setPlaybackMode:(SDAnimatedImagePlaybackMode)playbackMode {
_playbackMode = playbackMode;
self.player.playbackMode = playbackMode;
}
- (SDAnimatedImagePlaybackMode)playbackMode {
if (!_initFinished) {
return SDAnimatedImagePlaybackModeNormal; // Default mode is normal
}
return _playbackMode;
}
- (BOOL)shouldIncrementalLoad
{
if (!_initFinished) {
@ -402,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

@ -13,14 +13,6 @@
#import <MobileCoreServices/MobileCoreServices.h>
#endif
// iOS 8 Image/IO framework binary does not contains these APNG constants, so we define them. Thanks Apple :)
// We can not use runtime @available check for this issue, because it's a global symbol and should be loaded during launch time by dyld. So hack if the min deployment target version < iOS 9.0, whatever it running on iOS 9+ or not.
#if (__IPHONE_OS_VERSION_MIN_REQUIRED && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0)
const CFStringRef kCGImagePropertyAPNGLoopCount = (__bridge CFStringRef)@"LoopCount";
const CFStringRef kCGImagePropertyAPNGDelayTime = (__bridge CFStringRef)@"DelayTime";
const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef)@"UnclampedDelayTime";
#endif
@implementation SDImageAPNGCoder
+ (instancetype)sharedCoder {

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

@ -194,71 +194,73 @@ static NSString * _defaultDiskCacheDirectory;
[self.memoryCache setObject:image forKey:key cost:cost];
}
if (toDisk) {
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
if (!data && [image conformsToProtocol:@protocol(SDAnimatedImage)]) {
// If image is custom animated image class, prefer its original animated data
data = [((id<SDAnimatedImage>)image) animatedImageData];
}
if (!data && image) {
// Check image's associated image format, may return .undefined
SDImageFormat format = image.sd_imageFormat;
if (format == SDImageFormatUndefined) {
// If image is animated, use GIF (APNG may be better, but has bugs before macOS 10.14)
if (image.sd_isAnimated) {
format = SDImageFormatGIF;
} else {
// If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
if ([SDImageCoderHelper CGImageContainsAlpha:image.CGImage]) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
}
}
data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil];
}
[self _storeImageDataToDisk:data forKey:key];
if (image) {
// Check extended data
id extendedObject = image.sd_extendedObject;
if ([extendedObject conformsToProtocol:@protocol(NSCoding)]) {
NSData *extendedData;
if (@available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)) {
NSError *error;
extendedData = [NSKeyedArchiver archivedDataWithRootObject:extendedObject requiringSecureCoding:NO error:&error];
if (error) {
NSLog(@"NSKeyedArchiver archive failed with error: %@", error);
}
} else {
@try {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
extendedData = [NSKeyedArchiver archivedDataWithRootObject:extendedObject];
#pragma clang diagnostic pop
} @catch (NSException *exception) {
NSLog(@"NSKeyedArchiver archive failed with exception: %@", exception);
}
}
if (extendedData) {
[self.diskCache setExtendedData:extendedData forKey:key];
}
}
}
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
if (!toDisk) {
if (completionBlock) {
completionBlock();
}
return;
}
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
if (!data && [image conformsToProtocol:@protocol(SDAnimatedImage)]) {
// If image is custom animated image class, prefer its original animated data
data = [((id<SDAnimatedImage>)image) animatedImageData];
}
if (!data && image) {
// Check image's associated image format, may return .undefined
SDImageFormat format = image.sd_imageFormat;
if (format == SDImageFormatUndefined) {
// If image is animated, use GIF (APNG may be better, but has bugs before macOS 10.14)
if (image.sd_isAnimated) {
format = SDImageFormatGIF;
} else {
// If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
format = [SDImageCoderHelper CGImageContainsAlpha:image.CGImage] ? SDImageFormatPNG : SDImageFormatJPEG;
}
}
data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil];
}
[self _storeImageDataToDisk:data forKey:key];
[self _archivedDataWithImage:image forKey:key];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
- (void)_archivedDataWithImage:(UIImage *)image forKey:(NSString *)key {
if (!image) {
return;
}
// Check extended data
id extendedObject = image.sd_extendedObject;
if (![extendedObject conformsToProtocol:@protocol(NSCoding)]) {
return;
}
NSData *extendedData;
if (@available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)) {
NSError *error;
extendedData = [NSKeyedArchiver archivedDataWithRootObject:extendedObject requiringSecureCoding:NO error:&error];
if (error) {
NSLog(@"NSKeyedArchiver archive failed with error: %@", error);
}
} else {
@try {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
extendedData = [NSKeyedArchiver archivedDataWithRootObject:extendedObject];
#pragma clang diagnostic pop
} @catch (NSException *exception) {
NSLog(@"NSKeyedArchiver archive failed with exception: %@", exception);
}
}
if (extendedData) {
[self.diskCache setExtendedData:extendedData forKey:key];
}
}
@ -414,38 +416,43 @@ static NSString * _defaultDiskCacheDirectory;
}
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data options:(SDImageCacheOptions)options context:(SDWebImageContext *)context {
if (data) {
UIImage *image = SDImageCacheDecodeImageData(data, key, [[self class] imageOptionsFromCacheOptions:options], context);
if (image) {
// Check extended data
NSData *extendedData = [self.diskCache extendedDataForKey:key];
if (extendedData) {
id extendedObject;
if (@available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)) {
NSError *error;
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:extendedData error:&error];
unarchiver.requiresSecureCoding = NO;
extendedObject = [unarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:&error];
if (error) {
NSLog(@"NSKeyedUnarchiver unarchive failed with error: %@", error);
}
} else {
@try {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
extendedObject = [NSKeyedUnarchiver unarchiveObjectWithData:extendedData];
#pragma clang diagnostic pop
} @catch (NSException *exception) {
NSLog(@"NSKeyedUnarchiver unarchive failed with exception: %@", exception);
}
}
image.sd_extendedObject = extendedObject;
}
}
return image;
} else {
if (!data) {
return nil;
}
UIImage *image = SDImageCacheDecodeImageData(data, key, [[self class] imageOptionsFromCacheOptions:options], context);
[self _unarchiveObjectWithImage:image forKey:key];
return image;
}
- (void)_unarchiveObjectWithImage:(UIImage *)image forKey:(NSString *)key {
if (!image) {
return;
}
// Check extended data
NSData *extendedData = [self.diskCache extendedDataForKey:key];
if (!extendedData) {
return;
}
id extendedObject;
if (@available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)) {
NSError *error;
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:extendedData error:&error];
unarchiver.requiresSecureCoding = NO;
extendedObject = [unarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:&error];
if (error) {
NSLog(@"NSKeyedUnarchiver unarchive failed with error: %@", error);
}
} else {
@try {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
extendedObject = [NSKeyedUnarchiver unarchiveObjectWithData:extendedData];
#pragma clang diagnostic pop
} @catch (NSException *exception) {
NSLog(@"NSKeyedUnarchiver unarchive failed with exception: %@", exception);
}
}
image.sd_extendedObject = extendedObject;
}
- (nullable NSOperation *)queryCacheOperationForKey:(NSString *)key done:(SDImageCacheQueryCompletionBlock)doneBlock {
@ -537,13 +544,11 @@ static NSString * _defaultDiskCacheDirectory;
SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
}
if (shouldCacheToMomery) {
// decode image data only if in-memory cache missed
diskImage = [self diskImageForKey:key data:diskData options:options context:context];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
}
// decode image data only if in-memory cache missed
diskImage = [self diskImageForKey:key data:diskData options:options context:context];
if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
}
}

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,13 @@
@interface SDImageCodersManager ()
@property (nonatomic, strong, nonnull) dispatch_semaphore_t codersLock;
@property (nonatomic, strong, nonnull) NSMutableArray<id<SDImageCoder>> *imageCoders;
@property (atomic, assign) BOOL willTerminated;
@end
@implementation SDImageCodersManager {
NSMutableArray<id<SDImageCoder>> *_imageCoders;
SD_LOCK_DECLARE(_codersLock);
}
+ (nonnull instancetype)sharedManager {
@ -37,7 +37,6 @@
if (self = [super init]) {
// initialize with default coders
_imageCoders = [NSMutableArray arrayWithArray:@[[SDImageIOCoder sharedCoder], [SDImageGIFCoder sharedCoder], [SDImageAPNGCoder sharedCoder]]];
_codersLock = dispatch_semaphore_create(1);
_willTerminated = NO;
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self
@ -52,6 +51,7 @@
name:NSApplicationWillTerminateNotification
object:nil];
#endif
SD_LOCK_INIT(_codersLock);
}
return self;
}
@ -65,19 +65,19 @@
}
- (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
@ -86,18 +86,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

@ -18,8 +18,6 @@ static NSString * kSDCGImagePropertyHEICSUnclampedDelayTime = @"UnclampedDelayTi
@implementation SDImageHEICCoder
+ (void)initialize {
#if __IPHONE_13_0 || __TVOS_13_0 || __MAC_10_15 || __WATCHOS_6_0
// Xcode 11
if (@available(iOS 13, tvOS 13, macOS 10.15, watchOS 6, *)) {
// Use SDK instead of raw value
kSDCGImagePropertyHEICSDictionary = (__bridge NSString *)kCGImagePropertyHEICSDictionary;
@ -27,7 +25,6 @@ static NSString * kSDCGImagePropertyHEICSUnclampedDelayTime = @"UnclampedDelayTi
kSDCGImagePropertyHEICSDelayTime = (__bridge NSString *)kCGImagePropertyHEICSDelayTime;
kSDCGImagePropertyHEICSUnclampedDelayTime = (__bridge NSString *)kCGImagePropertyHEICSUnclampedDelayTime;
}
#endif
}
+ (instancetype)sharedCoder {

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

@ -12,13 +12,6 @@
#import "SDWebImageDownloaderResponseModifier.h"
#import "SDWebImageDownloaderDecryptor.h"
// iOS 8 Foundation.framework extern these symbol but the define is in CFNetwork.framework. We just fix this without import CFNetwork.framework
#if ((__IPHONE_OS_VERSION_MIN_REQUIRED && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0) || (__MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_11))
const float NSURLSessionTaskPriorityHigh = 0.75;
const float NSURLSessionTaskPriorityDefault = 0.5;
const float NSURLSessionTaskPriorityLow = 0.25;
#endif
static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed";
@ -151,7 +144,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
- (void)start {
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
if (!self.isFinished) self.finished = YES;
// Operation cancelled by user before sending the request
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user before sending the request"}]];
[self reset];
@ -225,8 +218,9 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:strongSelf];
});
} else {
if (!self.isFinished) self.finished = YES;
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
[self done];
[self reset];
}
}
@ -239,22 +233,28 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
- (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
__block typeof(self) strongSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:strongSelf];
});
if (self.dataTask) {
// Cancel the URLSession, `URLSession:task:didCompleteWithError:` delegate callback will be ignored
[self.dataTask cancel];
__block typeof(self) strongSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:strongSelf];
});
// As we cancelled the task, its callback won't be called and thus won't
// maintain the isFinished and isExecuting flags.
self.dataTask = nil;
}
// NSOperation disallow setFinished=YES **before** operation's start method been called
// We check for the initialized status, which is isExecuting == NO && isFinished = NO
// Ony update for non-intialized status, which is !(isExecuting == NO && isFinished = NO), or if (self.isExecuting || self.isFinished) {...}
if (self.isExecuting || self.isFinished) {
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
} else {
// Operation cancelled by user during sending the request
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}]];
}
// Operation cancelled by user during sending the request
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}]];
[self reset];
}

View File

@ -14,16 +14,6 @@
#import <QuartzCore/QuartzCore.h>
#endif
#if SD_UIKIT
#if __IPHONE_13_0 || __TVOS_13_0 || __MAC_10_15
// Xcode 11
#else
// Supports Xcode 10 users, for those users, define these enum
static NSInteger UIActivityIndicatorViewStyleMedium = 100;
static NSInteger UIActivityIndicatorViewStyleLarge = 101;
#endif
#endif
#pragma mark - Activity Indicator
@interface SDWebImageActivityIndicator ()

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,50 @@
*/
#import <Foundation/Foundation.h>
#import <os/lock.h>
#import <libkern/OSAtomic.h>
#import "SDmetamacros.h"
#define SD_USE_OS_UNFAIR_LOCK TARGET_OS_MACCATALYST ||\
(__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_10_0) ||\
(__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_12) ||\
(__TV_OS_VERSION_MIN_REQUIRED >= __TVOS_10_0) ||\
(__WATCH_OS_VERSION_MIN_REQUIRED >= __WATCHOS_3_0)
#ifndef SD_LOCK_DECLARE
#if SD_USE_OS_UNFAIR_LOCK
#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 SD_USE_OS_UNFAIR_LOCK
#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 SD_USE_OS_UNFAIR_LOCK
#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 SD_USE_OS_UNFAIR_LOCK
#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,6 +602,169 @@ static BOOL _isCalled;
expect(SDImageAPNGTestCoder.isCalled).equal(YES);
}
#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];
#if SD_UIKIT
[self.window addSubview:imageView];
#else
[self.window.contentView addSubview:imageView];
#endif
SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
imageView.autoPlayAnimatedImage = NO;
imageView.image = image;
__weak SDAnimatedImagePlayer *player = imageView.player;
player.playbackMode = SDAnimatedImagePlaybackModeReverse;
__block NSInteger i = player.totalFrameCount - 1;
__weak typeof(imageView) wimageView = imageView;
[player setAnimationFrameHandler:^(NSUInteger index, UIImage * _Nonnull frame) {
expect(index).equal(i);
expect(frame).notTo.beNil();
if (index == 0) {
[expectation fulfill];
// Stop Animation to avoid extra callback
[wimageView.player stopPlaying];
[wimageView removeFromSuperview];
return;
}
i--;
}];
[player startPlaying];
[self waitForExpectationsWithTimeout:15 handler:nil];
}
- (void)test34AnimatedImagePlaybackModeBounce {
XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView playback bounce mode"];
SDAnimatedImageView *imageView = [SDAnimatedImageView new];
#if SD_UIKIT
[self.window addSubview:imageView];
#else
[self.window.contentView addSubview:imageView];
#endif
SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
imageView.autoPlayAnimatedImage = NO;
imageView.image = image;
__weak SDAnimatedImagePlayer *player = imageView.player;
player.playbackMode = SDAnimatedImagePlaybackModeBounce;
__block NSInteger i = 0;
__block BOOL flag = false;
__block NSUInteger cnt = 0;
__weak typeof(imageView) wimageView = imageView;
[player setAnimationFrameHandler:^(NSUInteger index, UIImage * _Nonnull frame) {
expect(index).equal(i);
expect(frame).notTo.beNil();
if (index >= player.totalFrameCount - 1) {
cnt++;
flag = true;
} else if (cnt != 0 && index == 0) {
cnt++;
flag = false;
}
if (!flag) {
i++;
} else {
i--;
}
if (cnt >= 2) {
[expectation fulfill];
// Stop Animation to avoid extra callback
[wimageView.player stopPlaying];
[wimageView removeFromSuperview];
}
}];
[player startPlaying];
[self waitForExpectationsWithTimeout:15 handler:nil];
}
- (void)test35AnimatedImagePlaybackModeReversedBounce{
XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView playback reverse bounce mode"];
SDAnimatedImageView *imageView = [SDAnimatedImageView new];
#if SD_UIKIT
[self.window addSubview:imageView];
#else
[self.window.contentView addSubview:imageView];
#endif
SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
imageView.autoPlayAnimatedImage = NO;
imageView.image = image;
__weak SDAnimatedImagePlayer *player = imageView.player;
player.playbackMode = SDAnimatedImagePlaybackModeReversedBounce;
__block NSInteger i = player.totalFrameCount - 1;
__block BOOL flag = false;
__block NSUInteger cnt = 0;
__weak typeof(imageView) wimageView = imageView;
[player setAnimationFrameHandler:^(NSUInteger index, UIImage * _Nonnull frame) {
expect(index).equal(i);
expect(frame).notTo.beNil();
if (cnt != 0 && index >= player.totalFrameCount - 1) {
cnt++;
flag = false;
} else if (index == 0) {
cnt++;
flag = true;
}
if (flag) {
i++;
} else {
i--;
}
if (cnt >= 2) {
[expectation fulfill];
// Stop Animation to avoid extra callback
[wimageView.player stopPlaying];
[wimageView removeFromSuperview];
}
}];
[player startPlaying];
[self waitForExpectationsWithTimeout:15 handler:nil];
}
#pragma mark - Helper
- (UIWindow *)window {
if (!_window) {

View File

@ -632,6 +632,7 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
[[SDImageCache sharedImageCache] storeImageDataToDisk:data forKey:kTestImageKeyJPEG];
[[SDImageCachesManager sharedManager] queryImageForKey:kTestImageKeyJPEG options:0 context:@{SDWebImageContextStoreCacheType : @(SDImageCacheTypeDisk)} cacheType:SDImageCacheTypeAll completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
expect(image).notTo.beNil();
expect([[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG]).beNil();
[expectation fulfill];
}];

View File

@ -63,7 +63,7 @@
if (image && data && !error && finished) {
[expectation fulfill];
} else {
XCTFail(@"Something went wrong");
XCTFail(@"Something went wrong: %@", error.description);
}
}];
[self waitForExpectationsWithCommonTimeout];
@ -130,7 +130,7 @@
if (image && data && !error && finished) {
[expectation fulfill];
} else {
XCTFail(@"Something went wrong");
XCTFail(@"Something went wrong: %@", error.description);
}
}];
[self waitForExpectationsWithCommonTimeoutUsingHandler:^(NSError * _Nullable error) {
@ -145,7 +145,7 @@
if (image && data && !error && finished) {
[expectation fulfill];
} else if (finished) {
XCTFail(@"Something went wrong");
XCTFail(@"Something went wrong: %@", error.description);
} else {
// progressive updates
}
@ -161,7 +161,7 @@
if (!image && !data && error && finished) {
[expectation fulfill];
} else {
XCTFail(@"Something went wrong");
XCTFail(@"Something went wrong: %@", error.description);
}
}];
[self waitForExpectationsWithCommonTimeout];
@ -174,6 +174,7 @@
SDWebImageDownloadToken *token = [[SDWebImageDownloader sharedDownloader]
downloadImageWithURL:imageURL options:0 progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
expect(error).notTo.beNil();
expect(error.domain).equal(SDWebImageErrorDomain);
expect(error.code).equal(SDWebImageErrorCancelled);
}];
expect([SDWebImageDownloader sharedDownloader].currentDownloadCount).to.equal(1);
@ -227,7 +228,7 @@
if (image && data && !error && finished) {
[expectation fulfill];
} else {
XCTFail(@"Something went wrong");
XCTFail(@"Something went wrong: %@", error.description);
}
}];
@ -243,7 +244,7 @@
if (image && data && !error && finished) {
[expectation fulfill];
} else {
XCTFail(@"Something went wrong");
XCTFail(@"Something went wrong: %@", error.description);
}
}];
[self waitForExpectationsWithCommonTimeout];
@ -256,7 +257,7 @@
if (image && data && !error && finished) {
[expectation fulfill];
} else {
XCTFail(@"Something went wrong");
XCTFail(@"Something went wrong: %@", error.description);
}
}];
[self waitForExpectationsWithCommonTimeout];
@ -346,19 +347,15 @@
SDWebImageDownloaderConfig *config = SDWebImageDownloaderConfig.defaultDownloaderConfig;
config.minimumProgressInterval = 0.51; // This will make the progress only callback twice (once is 51%, another is 100%)
SDWebImageDownloader *downloader = [[SDWebImageDownloader alloc] initWithConfig:config];
NSURL *imageURL = [NSURL URLWithString:@"http://www.ioncannon.net/wp-content/uploads/2011/06/test2.webp"];
NSURL *imageURL = [NSURL URLWithString:@"https://raw.githubusercontent.com/recurser/exif-orientation-examples/master/Landscape_1.jpg"];
__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 > 2 && validProgressCount == 2) {
if (allProgressCount > 0) {
[expectation fulfill];
allProgressCount = 0;
return;
} else {
XCTFail(@"Progress callback more than once");
}
@ -376,7 +373,7 @@
if (image && data && !error && finished) {
[expectation fulfill];
} else if (finished) {
XCTFail(@"Something went wrong");
XCTFail(@"Something went wrong: %@", error.description);
} else {
// progressive updates
}
@ -391,7 +388,7 @@
if (image && data && !error && finished) {
[expectation fulfill];
} else if (finished) {
XCTFail(@"Something went wrong");
XCTFail(@"Something went wrong: %@", error.description);
} else {
// progressive updates
}
@ -428,7 +425,7 @@
if (image && data && !error && finished) {
[expectation fulfill];
} else {
XCTFail(@"Something went wrong");
XCTFail(@"Something went wrong: %@", error.description);
}
}];
expect(token2).toNot.beNil();
@ -470,7 +467,7 @@
[expectation fulfill];
} else {
NSLog(@"image = %@, data = %@, error = %@", image, data, error);
XCTFail(@"Something went wrong");
XCTFail(@"Something went wrong: %@", error.description);
}
}];
expect(token2).toNot.beNil();
@ -628,13 +625,10 @@
SDWebImageDownloaderDecryptor *decryptor = [SDWebImageDownloaderDecryptor decryptorWithBlock:^NSData * _Nullable(NSData * _Nonnull data, NSURLResponse * _Nullable response) {
if (@available(iOS 13, macOS 10.15, tvOS 13, *)) {
return [data decompressedDataUsingAlgorithm:NSDataCompressionAlgorithmZlib error:nil];
} else if (@available (iOS 9, macOS 10.11, tvOS 9, *)) {
} else {
NSMutableData *decodedData = [NSMutableData dataWithLength:10 * data.length];
compression_decode_buffer((uint8_t *)decodedData.bytes, decodedData.length, data.bytes, data.length, nil, COMPRESSION_ZLIB);
return [decodedData copy];
} else {
// iOS 8 does not have built-in Zlib support, just mock the data
return base64PNGData;
}
}];
// Note this is not a Zip Archive, just PNG raw buffer data using zlib compression
@ -692,6 +686,27 @@
}];
}
- (void)test27DownloadShouldCallbackWhenURLSessionRunning {
XCTestExpectation *expectation = [self expectationWithDescription:@"Downloader should callback when URLSessionTask running"];
NSURL *url = [NSURL URLWithString: @"https://raw.githubusercontent.com/SDWebImage/SDWebImage/master/SDWebImage_logo.png"];
NSString *key = [SDWebImageManager.sharedManager cacheKeyForURL:url];
[SDImageCache.sharedImageCache removeImageForKey:key withCompletion:^{
SDWebImageCombinedOperation *operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:0 progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
expect(error.domain).equal(SDWebImageErrorDomain);
expect(error.code).equal(SDWebImageErrorCancelled);
[expectation fulfill];
}];
dispatch_async(dispatch_get_main_queue(), ^{
[operation cancel];
});
}];
[self waitForExpectationsWithCommonTimeout];
}
#pragma mark - SDWebImageLoader
- (void)test30CustomImageLoaderWorks {
XCTestExpectation *expectation = [self expectationWithDescription:@"Custom image not works"];

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.3</string>
<string>5.10.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>5.9.3</string>
<string>5.10.2</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>