Merge branch 'master' of github.com:rs/SDWebImage

# Conflicts:
#	Tests/SDWebImage Tests.xcodeproj/project.pbxproj
This commit is contained in:
walkline 2017-09-09 14:57:58 +03:00
commit b9c17dcdd0
38 changed files with 2099 additions and 1668 deletions

View File

@ -1,3 +1,33 @@
## [4.1.0 - Swift API cleanup, on Jul 31st, 2017](https://github.com/rs/SDWebImage/releases/tag/4.1.0)
See [all tickets marked for the 4.1.0 release](https://github.com/rs/SDWebImage/milestone/13)
#### Features
- add ability to change `NSURLSessionConfiguration` used by `SDWebImageDownloader` #1891 fixes #1870
- support animated GIF on `macOS` #1975
- cleanup the Swift interface by making unavailable all methods with missing params that have alternatives - see #1797 - this may cause require some changes in the Swift code
#### Fixes
- handle `NSURLErrorNetworkConnectionLost` #1767
- fixed `CFBundleVersion` and `CFBundleShortVersionString` not valid for all platforms #1784 + 23a8be8 fixes #1780
- fixed `UIActivityIndicator` not always initialized on main thread #1802 + a6af214 fixes #1801
- `SDImageCacheConfig` forward declaration changed to import #1805
- making image downloading cache policy more clearer #1737
- added `@autoreleasepool` to `SDImageCache.storeImage` #1849
- fixed 32bit machine `long long` type transfer to NSInteger may become negative #1879
- fixed crash on multiple concurrent downloads when accessing `self.URLOperations` dictionary #1911 fixes #1909 #1950 #1835 #1838
- fixed crash due to incorrectly retained pointer to operation `self` which appears to create a dangled pointer #1940 fixes #1807 #1858 #1859 #1821 #1925 #1883 #1816 #1716
- fixed Swift naming collision (due to the Obj-C interface that offers multiple variants of the same method but with mixed and missing params) #1797 fixes #1764
- coding style #1971
- fixed Umbrella header warning for the FLAnimatedImage (while using Carthage) d9f7cf4 (replaces #1781) fixes #1776
- fixed issue where animated image arrays could be populated out of order (order of download) #1452
- fixed animated WebP decoding issue, including canvas size, the support for dispose method and the duration per frame #1952 (replaces #1694) fixes #1951
#### Docs
- #1778 #1779 #1788 #1799 b1c3bb7 (replaces #1806) 0df32ea #1847 5eb83c3 (replaces #1828) #1946 #1966
## [4.0.0 - New platforms (Mac OS X and watchOS) + refactoring, on Jan 28th, 2017](https://github.com/rs/SDWebImage/releases/tag/4.0.0)
See [all tickets marked for the 4.0.0 release](https://github.com/rs/SDWebImage/milestone/3)

View File

@ -74,6 +74,7 @@
@"http://www.ioncannon.net/wp-content/uploads/2011/06/test9.webp",
@"http://littlesvr.ca/apng/images/SteamEngine.webp",
@"http://littlesvr.ca/apng/images/world-cup-2014-42.webp",
@"https://isparta.github.io/compare-webp/image/gif_webp/webp/2.webp",
@"https://nr-platform.s3.amazonaws.com/uploads/platform/published_extension/branding_icon/275/AmazonS3.png",
nil];

View File

@ -27,6 +27,8 @@
// NOTE: https links or authentication ones do not work (there is a crash)
// Do any additional setup after loading the view.
// For animated GIF rendering, set `animates` to YES or will only show the first frame
self.imageView1.animates = YES;
[self.imageView1 sd_setImageWithURL:[NSURL URLWithString:@"http://assets.sbnation.com/assets/2512203/dogflops.gif"]];
[self.imageView2 sd_setImageWithURL:[NSURL URLWithString:@"http://www.ioncannon.net/wp-content/uploads/2011/06/test2.webp"]];
[self.imageView3 sd_setImageWithURL:[NSURL URLWithString:@"http://littlesvr.ca/apng/images/SteamEngine.webp"]];

View File

@ -92,7 +92,7 @@ imageView.sd_setImage(with: URL(string: "http://www.domain.com/path/to/image.jpg
- If you use cocoapods, add `pod 'SDWebImage/GIF'` to your podfile.
- To use it, simply make sure you use `FLAnimatedImageView` instead of `UIImageView`.
- **Note**: there is a backwards compatible feature, so if you are still trying to load a GIF into a `UIImageView`, it will only show the 1st frame as a static image.
- **Important**: FLAnimatedImage only works on the iOS platform, so for all the other platforms (OS X, tvOS, watchOS) we will fallback to the backwards compatibility feature described above
- **Important**: FLAnimatedImage only works on the iOS platform. For OS X, use `NSImageView` with `animates` set to `YES` to show the entire animated images and `NO` to only show the 1st frame. For all the other platforms (tvOS, watchOS) we will fallback to the backwards compatibility feature described above
## Common Problems
@ -145,7 +145,7 @@ There are three ways to use SDWebImage in your project:
#### Podfile
```
platform :ios, '7.0'
pod 'SDWebImage', '~>3.8'
pod 'SDWebImage', '~> 4.0'
```
If you are using Swift, be sure to add `use_frameworks!` and set your target to iOS 8+:
@ -197,6 +197,7 @@ community can help you solve it.
- [Konstantinos K.](https://github.com/mythodeia)
- [Bogdan Poplauschi](https://github.com/bpoplauschi)
- [Chester Liu](https://github.com/skyline75489)
- [DreamPiggy](https://github.com/dreampiggy)
## Licenses

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'SDWebImage'
s.version = '4.0.0'
s.version = '4.1.0'
s.osx.deployment_target = '10.8'
s.ios.deployment_target = '7.0'

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@
*
* @param url The url for the image.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url;
- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
/**
* Load the image at the given url (either from cache or download) and load it in this imageView. It works with both static and dynamic images
@ -42,7 +42,7 @@
* @param placeholder The image to be set initially, until the image request finishes.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder;
placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
/**
* Load the image at the given url (either from cache or download) and load it in this imageView. It works with both static and dynamic images
@ -55,7 +55,7 @@
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options;
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
/**
* Load the image at the given url (either from cache or download) and load it in this imageView. It works with both static and dynamic images
@ -86,7 +86,7 @@
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock;
completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;
/**
* Load the image at the given url (either from cache or download) and load it in this imageView. It works with both static and dynamic images

View File

@ -25,7 +25,7 @@
*
* @param url The url for the image.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url;
- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `image` with an `url` and a placeholder.
@ -37,7 +37,7 @@
* @see sd_setImageWithURL:placeholderImage:options:
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder;
placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.
@ -51,7 +51,7 @@
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options;
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `image` with an `url`.
@ -83,7 +83,7 @@
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock;
completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.

View File

@ -371,8 +371,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
image = [UIImage decodedImageWithImage:image];
}
return image;
}
else {
} else {
return nil;
}
}

View File

@ -15,7 +15,7 @@
// Apple's defines from TargetConditionals.h are a bit weird.
// Seems like TARGET_OS_MAC is always defined (on all platforms).
// To determine if we are running on OSX, we can only relly on TARGET_OS_IPHONE=0 and all the other platforms
// To determine if we are running on OSX, we can only rely on TARGET_OS_IPHONE=0 and all the other platforms
#if !TARGET_OS_IPHONE && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_WATCH
#define SD_MAC 1
#else
@ -93,11 +93,11 @@
#define SDDispatchQueueSetterSementics assign
#endif
extern UIImage *SDScaledImageForKey(NSString *key, UIImage *image);
FOUNDATION_EXPORT UIImage *SDScaledImageForKey(NSString *key, UIImage *image);
typedef void(^SDWebImageNoParamsBlock)();
typedef void(^SDWebImageNoParamsBlock)(void);
extern NSString *const SDWebImageErrorDomain;
FOUNDATION_EXPORT NSString *const SDWebImageErrorDomain;
#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
@ -107,5 +107,3 @@ extern NSString *const SDWebImageErrorDomain;
dispatch_async(dispatch_get_main_queue(), block);\
}
#endif
static int64_t kAsyncTestTimeout = 5;

View File

@ -8,6 +8,8 @@
#import "SDWebImageCompat.h"
#import "objc/runtime.h"
#if !__has_feature(objc_arc)
#error SDWebImage is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
#endif
@ -26,10 +28,20 @@ inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullabl
for (UIImage *tempImage in image.images) {
[scaledImages addObject:SDScaledImageForKey(key, tempImage)];
}
return [UIImage animatedImageWithImages:scaledImages duration:image.duration];
}
else {
UIImage *animatedImage = [UIImage animatedImageWithImages:scaledImages duration:image.duration];
#ifdef SD_WEBP
if (animatedImage) {
SEL sd_webpLoopCount = NSSelectorFromString(@"sd_webpLoopCount");
NSNumber *value = objc_getAssociatedObject(image, sd_webpLoopCount);
NSInteger loopCount = value.integerValue;
if (loopCount) {
objc_setAssociatedObject(animatedImage, sd_webpLoopCount, @(loopCount), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
#endif
return animatedImage;
} else {
#if SD_WATCH
if ([[WKInterfaceDevice currentDevice] respondsToSelector:@selector(screenScale)]) {
#elif SD_UIKIT

View File

@ -115,16 +115,10 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
size_t bytesPerRow = kBytesPerPixel * destResolution.width;
// Allocate enough pixel data to hold the output image.
void* destBitmapData = malloc( bytesPerRow * destResolution.height );
if (destBitmapData == NULL) {
return image;
}
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
destContext = CGBitmapContextCreate(destBitmapData,
destContext = CGBitmapContextCreate(NULL,
destResolution.width,
destResolution.height,
kBitsPerComponent,
@ -133,7 +127,6 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
if (destContext == NULL) {
free(destBitmapData);
return image;
}
CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);

View File

@ -68,8 +68,8 @@ typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
SDWebImageDownloaderLIFOExecutionOrder
};
extern NSString * _Nonnull const SDWebImageDownloadStartNotification;
extern NSString * _Nonnull const SDWebImageDownloadStopNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification;
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL);
@ -119,6 +119,15 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
/**
* The configuration in use by the internal NSURLSession.
* Mutating this object directly has no effect.
*
* @see createNewSessionWithConfiguration:
*/
@property (readonly, nonatomic, nonnull) NSURLSessionConfiguration *sessionConfiguration;
/**
* Changes download operations execution order. Default value is `SDWebImageDownloaderFIFOExecutionOrder`.
*/
@ -230,4 +239,14 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
*/
- (void)cancelAllDownloads;
/**
* Forces SDWebImageDownloader to create and use a new NSURLSession that is
* initialized with the given configuration.
* *Note*: All existing download operations in the queue will be cancelled.
* *Note*: `timeoutIntervalForRequest` is going to be overwritten.
*
* @param sessionConfiguration The configuration to use for the new NSURLSession
*/
- (void)createNewSessionWithConfiguration:(nonnull NSURLSessionConfiguration *)sessionConfiguration;
@end

View File

@ -84,20 +84,30 @@
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
_downloadTimeout = 15.0;
sessionConfiguration.timeoutIntervalForRequest = _downloadTimeout;
/**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
[self createNewSessionWithConfiguration:sessionConfiguration];
}
return self;
}
- (void)createNewSessionWithConfiguration:(NSURLSessionConfiguration *)sessionConfiguration {
[self cancelAllDownloads];
if (self.session) {
[self.session invalidateAndCancel];
}
sessionConfiguration.timeoutIntervalForRequest = self.downloadTimeout;
/**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
}
- (void)dealloc {
[self.session invalidateAndCancel];
self.session = nil;
@ -109,8 +119,7 @@
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
if (value) {
self.HTTPHeaders[field] = value;
}
else {
} else {
[self.HTTPHeaders removeObjectForKey:field];
}
}
@ -131,6 +140,10 @@
return _downloadQueue.maxConcurrentOperationCount;
}
- (NSURLSessionConfiguration *)sessionConfiguration {
return self.session.configuration;
}
- (void)setOperationClass:(nullable Class)operationClass {
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperationInterface)]) {
_operationClass = operationClass;
@ -211,7 +224,7 @@
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(nullable NSURL *)url
createCallback:(SDWebImageDownloaderOperation *(^)())createCallback {
createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
if (url == nil) {
if (completedBlock != nil) {
@ -230,11 +243,13 @@
__weak SDWebImageDownloaderOperation *woperation = operation;
operation.completionBlock = ^{
SDWebImageDownloaderOperation *soperation = woperation;
if (!soperation) return;
if (self.URLOperations[url] == soperation) {
[self.URLOperations removeObjectForKey:url];
};
dispatch_barrier_sync(self.barrierQueue, ^{
SDWebImageDownloaderOperation *soperation = woperation;
if (!soperation) return;
if (self.URLOperations[url] == soperation) {
[self.URLOperations removeObjectForKey:url];
};
});
};
}
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
@ -248,7 +263,7 @@
}
- (void)setSuspended:(BOOL)suspended {
(self.downloadQueue).suspended = suspended;
self.downloadQueue.suspended = suspended;
}
- (void)cancelAllDownloads {

View File

@ -10,10 +10,10 @@
#import "SDWebImageDownloader.h"
#import "SDWebImageOperation.h"
extern NSString * _Nonnull const SDWebImageDownloadStartNotification;
extern NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification;
extern NSString * _Nonnull const SDWebImageDownloadStopNotification;
extern NSString * _Nonnull const SDWebImageDownloadFinishNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification;

View File

@ -169,8 +169,9 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
});
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
@ -201,8 +202,9 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
if (self.dataTask) {
[self.dataTask cancel];
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
// As we cancelled the connection, its callback won't be called and thus won't
@ -221,8 +223,9 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
}
- (void)reset {
__weak typeof(self) weakSelf = self;
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks removeAllObjects];
[weakSelf.callbackBlocks removeAllObjects];
});
self.dataTask = nil;
self.imageData = nil;
@ -266,11 +269,11 @@ didReceiveResponse:(NSURLResponse *)response
self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
self.response = response;
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:weakSelf];
});
}
else {
} else {
NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
//This is the case when server returns '304 Not Modified'. It means that remote image is not changed.
@ -280,8 +283,9 @@ didReceiveResponse:(NSURLResponse *)response
} else {
[self.dataTask cancel];
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];
@ -373,7 +377,9 @@ didReceiveResponse:(NSURLResponse *)response
}
}
CFRelease(imageSource);
if (imageSource) {
CFRelease(imageSource);
}
}
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
@ -402,10 +408,11 @@ didReceiveResponse:(NSURLResponse *)response
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
@synchronized(self) {
self.dataTask = nil;
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:weakSelf];
}
});
}

View File

@ -61,7 +61,7 @@ typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls,
/**
* Queue options for Prefetcher. Defaults to Main Queue.
*/
@property (nonatomic, assign, nonnull) dispatch_queue_t prefetcherQueue;
@property (SDDispatchQueueSetterSementics, nonatomic, nonnull) dispatch_queue_t prefetcherQueue;
@property (weak, nonatomic, nullable) id <SDWebImagePrefetcherDelegate> delegate;

View File

@ -17,13 +17,13 @@
*/
@interface UIButton (WebCache)
#pragma mark - Image
/**
* Get the current image URL.
*/
- (nullable NSURL *)sd_currentImageURL;
#pragma mark - Image
/**
* Get the image URL for a control state.
*
@ -40,7 +40,7 @@
* @param state The state that uses the specified title. The values are described in UIControlState.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state;
forState:(UIControlState)state NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `image` with an `url` and a placeholder.
@ -54,7 +54,7 @@
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder;
placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.
@ -69,7 +69,7 @@
- (void)sd_setImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options;
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `image` with an `url`.
@ -105,7 +105,7 @@
- (void)sd_setImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock;
completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.
@ -130,6 +130,18 @@
#pragma mark - Background image
/**
* Get the current background image URL.
*/
- (nullable NSURL *)sd_currentBackgroundImageURL;
/**
* Get the background image URL for a control state.
*
* @param state Which state you want to know the URL for. The values are described in UIControlState.
*/
- (nullable NSURL *)sd_backgroundImageURLForState:(UIControlState)state;
/**
* Set the backgroundImageView `image` with an `url`.
*
@ -139,7 +151,7 @@
* @param state The state that uses the specified title. The values are described in UIControlState.
*/
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state;
forState:(UIControlState)state NS_REFINED_FOR_SWIFT;
/**
* Set the backgroundImageView `image` with an `url` and a placeholder.
@ -153,7 +165,7 @@
*/
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder;
placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
/**
* Set the backgroundImageView `image` with an `url`, placeholder and custom options.
@ -168,7 +180,7 @@
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options;
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
/**
* Set the backgroundImageView `image` with an `url`.
@ -204,7 +216,7 @@
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock;
completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;
/**
* Set the backgroundImageView `image` with an `url`, placeholder and custom options.

View File

@ -16,26 +16,34 @@
static char imageURLStorageKey;
typedef NSMutableDictionary<NSNumber *, NSURL *> SDStateImageURLDictionary;
typedef NSMutableDictionary<NSString *, NSURL *> SDStateImageURLDictionary;
static inline NSString * imageURLKeyForState(UIControlState state) {
return [NSString stringWithFormat:@"image_%lu", (unsigned long)state];
}
static inline NSString * backgroundImageURLKeyForState(UIControlState state) {
return [NSString stringWithFormat:@"backgroundImage_%lu", (unsigned long)state];
}
@implementation UIButton (WebCache)
#pragma mark - Image
- (nullable NSURL *)sd_currentImageURL {
NSURL *url = self.imageURLStorage[@(self.state)];
NSURL *url = self.imageURLStorage[imageURLKeyForState(self.state)];
if (!url) {
url = self.imageURLStorage[@(UIControlStateNormal)];
url = self.imageURLStorage[imageURLKeyForState(UIControlStateNormal)];
}
return url;
}
- (nullable NSURL *)sd_imageURLForState:(UIControlState)state {
return self.imageURLStorage[@(state)];
return self.imageURLStorage[imageURLKeyForState(state)];
}
#pragma mark - Image
- (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state {
[self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
}
@ -62,11 +70,11 @@ typedef NSMutableDictionary<NSNumber *, NSURL *> SDStateImageURLDictionary;
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock {
if (!url) {
[self.imageURLStorage removeObjectForKey:@(state)];
[self.imageURLStorage removeObjectForKey:imageURLKeyForState(state)];
return;
}
self.imageURLStorage[@(state)] = url;
self.imageURLStorage[imageURLKeyForState(state)] = url;
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
@ -82,6 +90,20 @@ typedef NSMutableDictionary<NSNumber *, NSURL *> SDStateImageURLDictionary;
#pragma mark - Background image
- (nullable NSURL *)sd_currentBackgroundImageURL {
NSURL *url = self.imageURLStorage[backgroundImageURLKeyForState(self.state)];
if (!url) {
url = self.imageURLStorage[backgroundImageURLKeyForState(UIControlStateNormal)];
}
return url;
}
- (nullable NSURL *)sd_backgroundImageURLForState:(UIControlState)state {
return self.imageURLStorage[backgroundImageURLKeyForState(state)];
}
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
}
@ -108,11 +130,11 @@ typedef NSMutableDictionary<NSNumber *, NSURL *> SDStateImageURLDictionary;
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock {
if (!url) {
[self.imageURLStorage removeObjectForKey:@(state)];
[self.imageURLStorage removeObjectForKey:backgroundImageURLKeyForState(state)];
return;
}
self.imageURLStorage[@(state)] = url;
self.imageURLStorage[backgroundImageURLKeyForState(state)] = url;
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url

View File

@ -18,6 +18,10 @@
if (!data) {
return nil;
}
#if SD_MAC
return [[UIImage alloc] initWithData:data];
#else
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
@ -42,8 +46,6 @@
#if SD_UIKIT || SD_WATCH
UIImage *frameImage = [UIImage imageWithCGImage:CGImage scale:scale orientation:UIImageOrientationUp];
staticImage = [UIImage animatedImageWithImages:@[frameImage] duration:0.0f];
#elif SD_MAC
staticImage = [[UIImage alloc] initWithCGImage:CGImage size:NSZeroSize];
#endif
CGImageRelease(CGImage);
}
@ -51,6 +53,7 @@
CFRelease(source);
return staticImage;
#endif
}
- (BOOL)isGIF {

View File

@ -12,6 +12,16 @@
@interface UIImage (WebP)
/**
* Get the current WebP image loop count, the default value is 0.
* For static WebP image, the value is 0.
* For animated WebP image, 0 means repeat the animation indefinitely.
* Note that because of the limitations of categories this property can get out of sync
* if you create another instance with CGImage or other methods.
* @return WebP image loop count
*/
- (NSInteger)sd_webpLoopCount;
+ (nullable UIImage *)sd_imageWithWebPData:(nullable NSData *)data;
@end

View File

@ -14,6 +14,8 @@
#import "webp/demux.h"
#import "NSImage+WebCache.h"
#import "objc/runtime.h"
// Callback for CGDataProviderRelease
static void FreeImageData(void *info, const void *data, size_t size) {
free((void *)data);
@ -21,6 +23,12 @@ static void FreeImageData(void *info, const void *data, size_t size) {
@implementation UIImage (WebP)
- (NSInteger)sd_webpLoopCount
{
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_webpLoopCount));
return value.integerValue;
}
+ (nullable UIImage *)sd_imageWithWebPData:(nullable NSData *)data {
if (!data) {
return nil;
@ -38,7 +46,7 @@ static void FreeImageData(void *info, const void *data, size_t size) {
uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
if (!(flags & ANIMATION_FLAG)) {
// for static single webp image
UIImage *staticImage = [self sd_rawWepImageWithData:webpData];
UIImage *staticImage = [self sd_rawWebpImageWithData:webpData];
WebPDemuxDelete(demuxer);
return staticImage;
}
@ -50,15 +58,37 @@ static void FreeImageData(void *info, const void *data, size_t size) {
return nil;
}
NSMutableArray *images = [NSMutableArray array];
NSTimeInterval duration = 0;
#if SD_UIKIT || SD_WATCH
int loopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);
int frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
#endif
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
CGBitmapInfo bitmapInfo;
if (!(flags & ALPHA_FLAG)) {
bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast;
} else {
bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
}
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8, 0, SDCGColorSpaceGetDeviceRGB(), bitmapInfo);
if (!canvas) {
WebPDemuxReleaseIterator(&iter);
WebPDemuxDelete(demuxer);
return nil;
}
NSMutableArray<UIImage *> *images = [NSMutableArray array];
#if SD_UIKIT || SD_WATCH
NSTimeInterval totalDuration = 0;
int durations[frameCount];
#endif
do {
UIImage *image;
if (iter.blend_method == WEBP_MUX_BLEND) {
image = [self sd_blendWebpImageWithOriginImage:[images lastObject] iterator:iter];
image = [self sd_blendWebpImageWithCanvas:canvas iterator:iter];
} else {
image = [self sd_rawWepImageWithData:iter.fragment];
image = [self sd_nonblendWebpImageWithCanvas:canvas iterator:iter];
}
if (!image) {
@ -66,46 +96,56 @@ static void FreeImageData(void *info, const void *data, size_t size) {
}
[images addObject:image];
duration += iter.duration / 1000.0f;
#if SD_MAC
break;
#else
int duration = iter.duration;
if (duration <= 10) {
// WebP standard says 0 duration is used for canvas updating but not showing image, but actually Chrome and other implementations set it to 100ms if duration is lower or equal than 10ms
// Some animated WebP images also created without duration, we should keep compatibility
duration = 100;
}
totalDuration += duration;
size_t count = images.count;
durations[count - 1] = duration;
#endif
} while (WebPDemuxNextFrame(&iter));
WebPDemuxReleaseIterator(&iter);
WebPDemuxDelete(demuxer);
CGContextRelease(canvas);
UIImage *finalImage = nil;
#if SD_UIKIT || SD_WATCH
finalImage = [UIImage animatedImageWithImages:images duration:duration];
#elif SD_MAC
if ([images count] > 0) {
finalImage = images[0];
NSArray<UIImage *> *animatedImages = [self sd_animatedImagesWithImages:images durations:durations totalDuration:totalDuration];
finalImage = [UIImage animatedImageWithImages:animatedImages duration:totalDuration / 1000.0];
if (finalImage) {
objc_setAssociatedObject(finalImage, @selector(sd_webpLoopCount), @(loopCount), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#elif SD_MAC
finalImage = images.firstObject;
#endif
return finalImage;
}
+ (nullable UIImage *)sd_blendWebpImageWithOriginImage:(nullable UIImage *)originImage iterator:(WebPIterator)iter {
if (!originImage) {
return nil;
}
CGSize size = originImage.size;
CGFloat tmpX = iter.x_offset;
CGFloat tmpY = size.height - iter.height - iter.y_offset;
CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
UIImage *image = [self sd_rawWepImageWithData:iter.fragment];
+ (nullable UIImage *)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter {
UIImage *image = [self sd_rawWebpImageWithData:iter.fragment];
if (!image) {
return nil;
}
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
uint32_t bitmapInfo = iter.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : 0;
CGContextRef blendCanvas = CGBitmapContextCreate(NULL, size.width, size.height, 8, 0, colorSpaceRef, bitmapInfo);
CGContextDrawImage(blendCanvas, CGRectMake(0, 0, size.width, size.height), originImage.CGImage);
CGContextDrawImage(blendCanvas, imageRect, image.CGImage);
CGImageRef newImageRef = CGBitmapContextCreateImage(blendCanvas);
size_t canvasWidth = CGBitmapContextGetWidth(canvas);
size_t canvasHeight = CGBitmapContextGetHeight(canvas);
CGSize size = CGSizeMake(canvasWidth, canvasHeight);
CGFloat tmpX = iter.x_offset;
CGFloat tmpY = size.height - iter.height - iter.y_offset;
CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
CGContextDrawImage(canvas, imageRect, image.CGImage);
CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
#if SD_UIKIT || SD_WATCH
image = [UIImage imageWithCGImage:newImageRef];
@ -114,13 +154,47 @@ static void FreeImageData(void *info, const void *data, size_t size) {
#endif
CGImageRelease(newImageRef);
CGContextRelease(blendCanvas);
CGColorSpaceRelease(colorSpaceRef);
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
CGContextClearRect(canvas, imageRect);
}
return image;
}
+ (nullable UIImage *)sd_rawWepImageWithData:(WebPData)webpData {
+ (nullable UIImage *)sd_nonblendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter {
UIImage *image = [self sd_rawWebpImageWithData:iter.fragment];
if (!image) {
return nil;
}
size_t canvasWidth = CGBitmapContextGetWidth(canvas);
size_t canvasHeight = CGBitmapContextGetHeight(canvas);
CGSize size = CGSizeMake(canvasWidth, canvasHeight);
CGFloat tmpX = iter.x_offset;
CGFloat tmpY = size.height - iter.height - iter.y_offset;
CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
CGContextClearRect(canvas, imageRect);
CGContextDrawImage(canvas, imageRect, image.CGImage);
CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
#if SD_UIKIT || SD_WATCH
image = [UIImage imageWithCGImage:newImageRef];
#elif SD_MAC
image = [[UIImage alloc] initWithCGImage:newImageRef size:NSZeroSize];
#endif
CGImageRelease(newImageRef);
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
CGContextClearRect(canvas, imageRect);
}
return image;
}
+ (nullable UIImage *)sd_rawWebpImageWithData:(WebPData)webpData {
WebPDecoderConfig config;
if (!WebPInitDecoderConfig(&config)) {
return nil;
@ -133,7 +207,7 @@ static void FreeImageData(void *info, const void *data, size_t size) {
config.output.colorspace = config.input.has_alpha ? MODE_rgbA : MODE_RGB;
config.options.use_threads = 1;
// Decode the WebP image data into a RGBA value array.
// Decode the WebP image data into a RGBA value array
if (WebPDecode(webpData.bytes, webpData.size, &config) != VP8_STATUS_OK) {
return nil;
}
@ -145,16 +219,15 @@ static void FreeImageData(void *info, const void *data, size_t size) {
height = config.options.scaled_height;
}
// Construct a UIImage from the decoded RGBA value array.
// Construct a UIImage from the decoded RGBA value array
CGDataProviderRef provider =
CGDataProviderCreateWithData(NULL, config.output.u.RGBA.rgba, config.output.u.RGBA.size, FreeImageData);
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = config.input.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : 0;
CGColorSpaceRef colorSpaceRef = SDCGColorSpaceGetDeviceRGB();
CGBitmapInfo bitmapInfo = config.input.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast;
size_t components = config.input.has_alpha ? 4 : 3;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
CGImageRef imageRef = CGImageCreate(width, height, 8, components * 8, components * width, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
CGColorSpaceRelease(colorSpaceRef);
CGDataProviderRelease(provider);
#if SD_UIKIT || SD_WATCH
@ -167,6 +240,63 @@ static void FreeImageData(void *info, const void *data, size_t size) {
return image;
}
+ (NSArray<UIImage *> *)sd_animatedImagesWithImages:(NSArray<UIImage *> *)images durations:(int const * const)durations totalDuration:(NSTimeInterval)totalDuration
{
// [UIImage animatedImageWithImages:duration:] only use the average duration for per frame
// divide the total duration to implement per frame duration for animated WebP
NSUInteger count = images.count;
if (!count) {
return nil;
}
if (count == 1) {
return images;
}
int const gcd = gcdArray(count, durations);
NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:count];
[images enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
int duration = durations[idx];
int repeatCount;
if (gcd) {
repeatCount = duration / gcd;
} else {
repeatCount = 1;
}
for (int i = 0; i < repeatCount; ++i) {
[animatedImages addObject:image];
}
}];
return animatedImages;
}
static CGColorSpaceRef SDCGColorSpaceGetDeviceRGB() {
static CGColorSpaceRef space;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
space = CGColorSpaceCreateDeviceRGB();
});
return space;
}
static int gcdArray(size_t const count, int const * const values) {
int result = values[0];
for (size_t i = 1; i < count; ++i) {
result = gcd(values[i], result);
}
return result;
}
static int gcd(int a,int b) {
int c;
while (a != 0) {
c = a;
a = b % a;
b = c;
}
return b;
}
@end
#endif

View File

@ -24,7 +24,7 @@
*
* @param url The url for the image.
*/
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url;
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `highlightedImage` with an `url` and custom options.
@ -35,7 +35,7 @@
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
*/
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options;
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `highlightedImage` with an `url`.
@ -50,7 +50,7 @@
* The fourth parameter is the original image url.
*/
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url
completed:(nullable SDExternalCompletionBlock)completedBlock;
completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `highlightedImage` with an `url` and custom options.

View File

@ -54,7 +54,7 @@
*
* @param url The url for the image.
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url;
- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `image` with an `url` and a placeholder.
@ -66,7 +66,7 @@
* @see sd_setImageWithURL:placeholderImage:options:
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder;
placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.
@ -79,7 +79,7 @@
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options;
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `image` with an `url`.
@ -111,7 +111,7 @@
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock;
completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.

View File

@ -75,7 +75,7 @@
NSMutableArray<id<SDWebImageOperation>> *operationsArray = [[NSMutableArray alloc] init];
for (NSURL *logoImageURL in arrayOfURLs) {
[arrayOfURLs enumerateObjectsUsingBlock:^(NSURL *logoImageURL, NSUInteger idx, BOOL * _Nonnull stop) {
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:logoImageURL options:0 progress:nil completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!wself) return;
dispatch_main_async_safe(^{
@ -86,7 +86,16 @@
if (!currentImages) {
currentImages = [[NSMutableArray alloc] init];
}
[currentImages addObject:image];
// We know what index objects should be at when they are returned so
// we will put the object at the index, filling any empty indexes
// with the image that was returned too "early". These images will
// be overwritten. (does not require additional sorting datastructure)
while ([currentImages count] < idx) {
[currentImages addObject:image];
}
currentImages[idx] = image;
sself.animationImages = currentImages;
[sself setNeedsLayout];
@ -95,7 +104,7 @@
});
}];
[operationsArray addObject:operation];
}
}];
[self sd_setImageLoadOperation:[operationsArray copy] forKey:@"UIImageViewAnimationImages"];
}

View File

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
1E3C51E919B46E370092B5E6 /* SDWebImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */; };
37D122881EC48B5E00D98CEB /* MockFileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 37D122871EC48B5E00D98CEB /* MockFileManager.m */; };
2D7AF0601F329763000083C2 /* SDTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D7AF05F1F329763000083C2 /* SDTestCase.m */; };
433BBBB51D7EF5C00086B6E9 /* SDWebImageDecoderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 433BBBB41D7EF5C00086B6E9 /* SDWebImageDecoderTests.m */; };
433BBBB71D7EF8200086B6E9 /* TestImage.gif in Resources */ = {isa = PBXBuildFile; fileRef = 433BBBB61D7EF8200086B6E9 /* TestImage.gif */; };
433BBBB91D7EF8260086B6E9 /* TestImage.png in Resources */ = {isa = PBXBuildFile; fileRef = 433BBBB81D7EF8260086B6E9 /* TestImage.png */; };
@ -32,6 +33,8 @@
1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageDownloaderTests.m; sourceTree = "<group>"; };
37D122861EC48B5E00D98CEB /* MockFileManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MockFileManager.h; sourceTree = "<group>"; };
37D122871EC48B5E00D98CEB /* MockFileManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MockFileManager.m; sourceTree = "<group>"; };
2D7AF05E1F329763000083C2 /* SDTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDTestCase.h; sourceTree = "<group>"; };
2D7AF05F1F329763000083C2 /* SDTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDTestCase.m; sourceTree = "<group>"; };
433BBBB41D7EF5C00086B6E9 /* SDWebImageDecoderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageDecoderTests.m; sourceTree = "<group>"; };
433BBBB61D7EF8200086B6E9 /* TestImage.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = TestImage.gif; sourceTree = "<group>"; };
433BBBB81D7EF8260086B6E9 /* TestImage.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = TestImage.png; sourceTree = "<group>"; };
@ -125,6 +128,8 @@
4369C2731D9804B1007E863A /* SDCategoriesTests.m */,
37D122861EC48B5E00D98CEB /* MockFileManager.h */,
37D122871EC48B5E00D98CEB /* MockFileManager.m */,
2D7AF05E1F329763000083C2 /* SDTestCase.h */,
2D7AF05F1F329763000083C2 /* SDTestCase.m */,
);
path = Tests;
sourceTree = "<group>";
@ -259,6 +264,7 @@
1E3C51E919B46E370092B5E6 /* SDWebImageDownloaderTests.m in Sources */,
37D122881EC48B5E00D98CEB /* MockFileManager.m in Sources */,
4369C2741D9804B1007E863A /* SDCategoriesTests.m in Sources */,
2D7AF0601F329763000083C2 /* SDTestCase.m in Sources */,
4369C1D11D97F80F007E863A /* SDWebImagePrefetcherTests.m in Sources */,
DA248D69195475D800390AB0 /* SDImageCacheTests.m in Sources */,
DA248D6B195476AC00390AB0 /* SDWebImageManagerTests.m in Sources */,

View File

@ -7,11 +7,7 @@
* file that was distributed with this source code.
*/
#define EXP_SHORTHAND // required by Expecta
#import <XCTest/XCTest.h>
#import <Expecta/Expecta.h>
#import "SDTestCase.h"
#import <SDWebImage/UIImageView+WebCache.h>
#import <SDWebImage/UIImageView+HighlightedWebCache.h>
#import <SDWebImage/MKAnnotationView+WebCache.h>
@ -20,7 +16,7 @@
@import FLAnimatedImage;
@interface SDCategoriesTests : XCTestCase
@interface SDCategoriesTests : SDTestCase
@end
@ -39,7 +35,7 @@
expect(imageView.image).to.equal(image);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)testUIImageViewSetHighlightedImageWithURL {
@ -55,7 +51,7 @@
expect(imageView.highlightedImage).to.equal(image);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)testMKAnnotationViewSetImageWithURL {
@ -71,7 +67,7 @@
expect(annotationView.image).to.equal(image);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)testUIButtonSetImageWithURLNormalState {
@ -88,7 +84,7 @@
expect([button imageForState:UIControlStateNormal]).to.equal(image);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)testUIButtonSetImageWithURLHighlightedState {
@ -105,7 +101,7 @@
expect([button imageForState:UIControlStateHighlighted]).to.equal(image);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)testUIButtonSetBackgroundImageWithURLNormalState {
@ -122,7 +118,7 @@
expect([button backgroundImageForState:UIControlStateNormal]).to.equal(image);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)testFLAnimatedImageViewSetImageWithURL {
@ -140,7 +136,7 @@
expect(imageView.animatedImage).toNot.beNil();
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
@end

View File

@ -6,18 +6,13 @@
* file that was distributed with this source code.
*/
#define EXP_SHORTHAND // required by Expecta
#import <XCTest/XCTest.h>
#import <Expecta/Expecta.h>
#import "SDTestCase.h"
#import <SDWebImage/SDImageCache.h>
#import "MockFileManager.h"
NSString *kImageTestKey = @"TestImageKey.jpg";
@interface SDImageCacheTests : XCTestCase
@interface SDImageCacheTests : SDTestCase
@property (strong, nonatomic) SDImageCache *sharedImageCache;
@end
@ -57,7 +52,7 @@ NSString *kImageTestKey = @"TestImageKey.jpg";
}];
expect([self.sharedImageCache imageFromMemoryCacheForKey:kImageTestKey]).to.equal([self imageForTesting]);
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)test05ClearMemoryCache{
@ -73,7 +68,7 @@ NSString *kImageTestKey = @"TestImageKey.jpg";
XCTFail(@"Image should be in cache");
}
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
// Testing storeImage:forKey:
@ -90,7 +85,7 @@ NSString *kImageTestKey = @"TestImageKey.jpg";
XCTFail(@"Image should be in cache");
}
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
// Testing storeImage:forKey:toDisk:YES
@ -107,7 +102,7 @@ NSString *kImageTestKey = @"TestImageKey.jpg";
XCTFail(@"Image should be in cache");
}
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
// Testing storeImage:forKey:toDisk:NO
@ -126,7 +121,7 @@ NSString *kImageTestKey = @"TestImageKey.jpg";
}];
[self.sharedImageCache clearMemory];
expect([self.sharedImageCache imageFromMemoryCacheForKey:kImageTestKey]).to.beNil();
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)test09RetrieveImageThroughNSOperation{

23
Tests/Tests/SDTestCase.h Normal file
View File

@ -0,0 +1,23 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
* (c) Matt Galloway
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#define EXP_SHORTHAND // required by Expecta
#import <XCTest/XCTest.h>
#import <Expecta/Expecta.h>
extern const int64_t kAsyncTestTimeout;
@interface SDTestCase : XCTestCase
- (void)waitForExpectationsWithCommonTimeout;
- (void)waitForExpectationsWithCommonTimeoutUsingHandler:(XCWaitCompletionHandler)handler;
@end

24
Tests/Tests/SDTestCase.m Normal file
View File

@ -0,0 +1,24 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
* (c) Matt Galloway
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDTestCase.h"
const int64_t kAsyncTestTimeout = 5;
@implementation SDTestCase
- (void)waitForExpectationsWithCommonTimeout {
[self waitForExpectationsWithCommonTimeoutUsingHandler:nil];
}
- (void)waitForExpectationsWithCommonTimeoutUsingHandler:(XCWaitCompletionHandler)handler {
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:handler];
}
@end

View File

@ -7,14 +7,10 @@
* file that was distributed with this source code.
*/
#define EXP_SHORTHAND // required by Expecta
#import <XCTest/XCTest.h>
#import <Expecta/Expecta.h>
#import "SDTestCase.h"
#import <SDWebImage/SDWebImageDecoder.h>
@interface SDWebImageDecoderTests : XCTestCase
@interface SDWebImageDecoderTests : SDTestCase
@end

View File

@ -7,12 +7,7 @@
* file that was distributed with this source code.
*/
#define EXP_SHORTHAND // required by Expecta
#import <XCTest/XCTest.h>
#import <Expecta/Expecta.h>
#import "SDTestCase.h"
#import <SDWebImage/SDWebImageDownloader.h>
#import <SDWebImage/SDWebImageDownloaderOperation.h>
@ -55,7 +50,7 @@
@interface SDWebImageDownloaderTests : XCTestCase
@interface SDWebImageDownloaderTests : SDTestCase
@end
@ -91,8 +86,7 @@
XCTFail(@"Something went wrong");
}
}];
expect([SDWebImageDownloader sharedDownloader].currentDownloadCount).to.equal(1);
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)test05ThatSetAndGetMaxConcurrentDownloadsWorks {
@ -142,8 +136,7 @@
XCTFail(@"Something went wrong");
}
}];
expect([SDWebImageDownloader sharedDownloader].currentDownloadCount).to.equal(1);
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
[SDWebImageDownloader sharedDownloader].username = nil;
[SDWebImageDownloader sharedDownloader].password = nil;
}
@ -160,8 +153,7 @@
// progressive updates
}
}];
expect([SDWebImageDownloader sharedDownloader].currentDownloadCount).to.equal(1);
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)test10That404CaseCallsCompletionWithError {
@ -175,8 +167,7 @@
XCTFail(@"Something went wrong");
}
}];
expect([SDWebImageDownloader sharedDownloader].currentDownloadCount).to.equal(1);
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)test11ThatCancelWorks {
@ -197,7 +188,7 @@
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)test12ThatWeCanUseAnotherSessionForEachDownloadOperation {
@ -223,7 +214,7 @@
[operation start];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)test13ThatDownloadCanContinueWhenTheAppEntersBackground {
@ -236,8 +227,7 @@
XCTFail(@"Something went wrong");
}
}];
expect([SDWebImageDownloader sharedDownloader].currentDownloadCount).to.equal(1);
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)test14ThatPNGWorks {
@ -250,8 +240,7 @@
XCTFail(@"Something went wrong");
}
}];
expect([SDWebImageDownloader sharedDownloader].currentDownloadCount).to.equal(1);
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)test15ThatWEBPWorks {
@ -264,8 +253,7 @@
XCTFail(@"Something went wrong");
}
}];
expect([SDWebImageDownloader sharedDownloader].currentDownloadCount).to.equal(1);
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
/**
@ -303,7 +291,7 @@
[[SDWebImageDownloader sharedDownloader] cancel:token1];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
/**
@ -342,7 +330,7 @@
}];
expect(token2).toNot.beNil();
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
@end

View File

@ -6,17 +6,12 @@
* file that was distributed with this source code.
*/
#define EXP_SHORTHAND // required by Expecta
#import <XCTest/XCTest.h>
#import <Expecta/Expecta.h>
#import "SDTestCase.h"
#import <SDWebImage/SDWebImageManager.h>
NSString *workingImageURL = @"http://s3.amazonaws.com/fast-image-cache/demo-images/FICDDemoImage001.jpg";
@interface SDWebImageManagerTests : XCTestCase
@interface SDWebImageManagerTests : SDTestCase
@end
@ -45,7 +40,7 @@ NSString *workingImageURL = @"http://s3.amazonaws.com/fast-image-cache/demo-imag
}];
expect([[SDWebImageManager sharedManager] isRunning]).to.equal(YES);
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)test03ThatDownloadWithIncorrectURLInvokesCompletionBlockWithAnErrorAsync {
@ -65,7 +60,7 @@ NSString *workingImageURL = @"http://s3.amazonaws.com/fast-image-cache/demo-imag
expectation = nil;
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)test04CachedImageExistsForURL {
@ -91,7 +86,7 @@ NSString *workingImageURL = @"http://s3.amazonaws.com/fast-image-cache/demo-imag
XCTFail(@"Image should be in cache");
}
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)test06CancellAll {
@ -110,7 +105,7 @@ NSString *workingImageURL = @"http://s3.amazonaws.com/fast-image-cache/demo-imag
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
@end

View File

@ -7,15 +7,10 @@
* file that was distributed with this source code.
*/
#define EXP_SHORTHAND // required by Expecta
#import <XCTest/XCTest.h>
#import <Expecta/Expecta.h>
#import "SDTestCase.h"
#import <SDWebImage/SDWebImagePrefetcher.h>
@interface SDWebImagePrefetcherTests : XCTestCase
@interface SDWebImagePrefetcherTests : SDTestCase
@end
@ -51,7 +46,7 @@
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
- (void)test03PrefetchWithEmptyArrayWillCallTheCompletionWithAllZeros {
@ -63,7 +58,7 @@
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
[self waitForExpectationsWithCommonTimeout];
}
// TODO: test the prefetcher delegate works

View File

@ -6,28 +6,51 @@
* file that was distributed with this source code.
*/
#define EXP_SHORTHAND // required by Expecta
#import <XCTest/XCTest.h>
#import <Expecta/Expecta.h>
#import "SDTestCase.h"
#import <SDWebImage/UIImage+MultiFormat.h>
@interface UIImageMultiFormatTests : XCTestCase
@interface UIImageMultiFormatTests : SDTestCase
@end
@implementation UIImageMultiFormatTests
- (void)testImageOrientationFromImageDataWithInvalidData {
- (void)test01ImageOrientationFromImageDataWithInvalidData {
// sync download image
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
SEL selector = @selector(sd_imageOrientationFromImageData:);
#pragma clang diagnostic pop
UIImageOrientation orientation = [[UIImage class] performSelector:selector withObject:nil];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
UIImageOrientation orientation = (UIImageOrientation)[[UIImage class] performSelector:selector withObject:nil];
#pragma clang diagnostic pop
expect(orientation).to.equal(UIImageOrientationUp);
}
- (void)test02AnimatedWebPImageArrayWithEqualSizeAndScale {
NSURL *webpURL = [NSURL URLWithString:@"https://isparta.github.io/compare-webp/image/gif_webp/webp/2.webp"];
NSData *data = [NSData dataWithContentsOfURL:webpURL];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
SEL selector = @selector(sd_imageWithWebPData:);
#pragma clang diagnostic pop
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
UIImage *animatedImage = [[UIImage class] performSelector:selector withObject:data];
#pragma clang diagnostic pop
CGSize imageSize = animatedImage.size;
CGFloat imageScale = animatedImage.scale;
[animatedImage.images enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
CGSize size = image.size;
CGFloat scale = image.scale;
expect(imageSize.width).to.equal(size.width);
expect(imageSize.height).to.equal(size.height);
expect(imageScale).to.equal(scale);
}];
}
@end

@ -1 +1 @@
Subproject commit 3d97bb75144147e47db278ec76e5e70c6b2243db
Subproject commit 50d1a848bc56554af8413cfe681f94286a6371b3

View File

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

View File

@ -34,16 +34,29 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[];
#import <SDWebImage/UIImage+MultiFormat.h>
#import <SDWebImage/SDWebImageOperation.h>
#import <SDWebImage/SDWebImageDownloader.h>
#if SD_MAC || SD_UIKIT
#import <SDWebImage/MKAnnotationView+WebCache.h>
#import <SDWebImage/MKAnnotationView+WebCache.h>
#endif
#import <SDWebImage/SDWebImageDecoder.h>
#import <SDWebImage/UIImage+WebP.h>
#import <SDWebImage/UIImage+GIF.h>
#import <SDWebImage/NSData+ImageContentType.h>
#if SD_MAC
#import <SDWebImage/NSImage+WebCache.h>
#import <SDWebImage/NSImage+WebCache.h>
#endif
#if SD_UIKIT
#import <SDWebImage/FLAnimatedImageView+WebCache.h>
#import <SDWebImage/FLAnimatedImageView+WebCache.h>
#if __has_include(<SDWebImage/FLAnimatedImage.h>)
#import <SDWebImage/FLAnimatedImage.h>
#endif
#if __has_include(<SDWebImage/FLAnimatedImageView.h>)
#import <SDWebImage/FLAnimatedImageView.h>
#endif
#endif