Merge branch 'master' of https://github.com/rs/SDWebImage into 5.x
* 'master' of https://github.com/rs/SDWebImage: Update the test and description to make it more clear Fix the issue that `setAnimationImagesWithURLs` weak reference may dealloc before the animated images was set Fix the strange file permissions issue Add the SDImageCacheQueryMemoryOnly to specify query memory only Update the cache options name to make it more clear Added missing param in storeImage examples Add a NSProgress property represent the image loading progress, this allow user add KVO on it for complicated logic
This commit is contained in:
commit
46a55ba6c7
|
@ -115,14 +115,16 @@ 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.
|
||||
You can prevent this from happening by calling the alternative method `imageFromMemoryCacheForKey:`.
|
||||
|
||||
To store an image into the cache, you use the storeImage:forKey: method:
|
||||
To store an image into the cache, you use the storeImage:forKey:completion: method:
|
||||
|
||||
```objective-c
|
||||
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];
|
||||
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey completion:^{
|
||||
// image stored
|
||||
}];
|
||||
```
|
||||
|
||||
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: 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
|
||||
|
@ -144,4 +146,4 @@ the URL before to use it as a cache key:
|
|||
// Your app init code...
|
||||
return YES;
|
||||
}
|
||||
```
|
||||
```
|
||||
|
|
|
@ -46,8 +46,8 @@
|
|||
options:(SDWebImageOptions)options
|
||||
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
||||
completed:(nullable SDExternalCompletionBlock)completedBlock {
|
||||
options |= SDWebImageQueryDiskDataWhenInMemory;
|
||||
options |= SDWebImageQueryDiskDataSync;
|
||||
options |= SDWebImageQueryDataWhenInMemory;
|
||||
options |= SDWebImageQueryDiskSync;
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
__weak typeof(self)weakSelf = self;
|
||||
[self sd_internalSetImageWithURL:url
|
||||
|
|
|
@ -27,13 +27,17 @@ typedef NS_ENUM(NSInteger, SDImageCacheType) {
|
|||
|
||||
typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
|
||||
/**
|
||||
* By default, we do not query disk cache when the image is cached in memory. This mask can force query disk data at the same time.
|
||||
* By default, we will query disk cache if the memory cache missed. This mask can force to query memory cache only without disk data.
|
||||
*/
|
||||
SDImageCacheQueryDiskDataWhenInMemory = 1 << 0,
|
||||
SDImageCacheQueryMemoryOnly = 1 << 0,
|
||||
/**
|
||||
* By default, we query the memory cache synchonized, disk cache asynchronized. This mask can force to query disk cache synchonized.
|
||||
* By default, we do not query disk data when the image is cached in memory. This mask can force to query disk data at the same time.
|
||||
*/
|
||||
SDImageCacheQueryDiskDataSync = 1 << 1
|
||||
SDImageCacheQueryDataWhenInMemory = 1 << 1,
|
||||
/**
|
||||
* By default, we query the memory cache synchronously, disk cache asynchronously. This mask can force to query disk cache synchronously.
|
||||
*/
|
||||
SDImageCacheQueryDiskSync = 1 << 2
|
||||
};
|
||||
|
||||
typedef void(^SDCacheQueryCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType);
|
||||
|
|
|
@ -399,7 +399,8 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
|
|||
|
||||
// First check the in-memory cache...
|
||||
UIImage *image = [self imageFromMemoryCacheForKey:key];
|
||||
if (image && !(options & SDImageCacheQueryDiskDataWhenInMemory)) {
|
||||
BOOL shouldQueryMemoryOnly = (options & SDImageCacheQueryMemoryOnly) || (image && !(options & SDImageCacheQueryDataWhenInMemory));
|
||||
if (shouldQueryMemoryOnly) {
|
||||
if (doneBlock) {
|
||||
doneBlock(image, nil, SDImageCacheTypeMemory);
|
||||
}
|
||||
|
@ -426,7 +427,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
|
|||
}
|
||||
|
||||
if (doneBlock) {
|
||||
if (options & SDImageCacheQueryDiskDataSync) {
|
||||
if (options & SDImageCacheQueryDiskSync) {
|
||||
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
@ -437,7 +438,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
|
|||
}
|
||||
};
|
||||
|
||||
if (options & SDImageCacheQueryDiskDataSync) {
|
||||
if (options & SDImageCacheQueryDiskSync) {
|
||||
queryDiskBlock();
|
||||
} else {
|
||||
dispatch_async(self.ioQueue, queryDiskBlock);
|
||||
|
|
|
@ -25,7 +25,7 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
|
|||
SDWebImageLowPriority = 1 << 1,
|
||||
|
||||
/**
|
||||
* This flag disables on-disk caching
|
||||
* This flag disables on-disk caching, including cache query and cache storing
|
||||
*/
|
||||
SDWebImageCacheMemoryOnly = 1 << 2,
|
||||
|
||||
|
@ -97,16 +97,16 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
|
|||
SDWebImageScaleDownLargeImages = 1 << 12,
|
||||
|
||||
/**
|
||||
* By default, we do not query disk cache when the image is cached in memory. This mask can force query disk data at the same time.
|
||||
* This options is recommend to be used with `SDWebImageQueryDiskDataSync` to ensure the image is loaded in the same runloop.
|
||||
* By default, we do not query disk data when the image is cached in memory. This mask can force to query disk data at the same time.
|
||||
* This options is recommend to be used with `SDWebImageQueryDiskSync` to ensure the image is loaded in the same runloop.
|
||||
*/
|
||||
SDWebImageQueryDiskDataWhenInMemory = 1 << 13,
|
||||
SDWebImageQueryDataWhenInMemory = 1 << 13,
|
||||
|
||||
/**
|
||||
* By default, we query the memory cache synchonized, disk cache asynchronized. This mask can force to query disk cache synchonized to ensure that image is loaded in the same runloop.
|
||||
* By default, we query the memory cache synchronously, disk cache asynchronously. This mask can force to query disk cache synchronously to ensure that image is loaded in the same runloop.
|
||||
* This can avoid flashing during cell reuse if you disable memory cache or in some other cases.
|
||||
*/
|
||||
SDWebImageQueryDiskDataSync = 1 << 14
|
||||
SDWebImageQueryDiskSync = 1 << 14
|
||||
};
|
||||
|
||||
typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL);
|
||||
|
|
|
@ -145,8 +145,9 @@
|
|||
NSString *key = [self cacheKeyForURL:url];
|
||||
|
||||
SDImageCacheOptions cacheOptions = 0;
|
||||
if (options & SDWebImageQueryDiskDataWhenInMemory) cacheOptions |= SDImageCacheQueryDiskDataWhenInMemory;
|
||||
if (options & SDWebImageQueryDiskDataSync) cacheOptions |= SDImageCacheQueryDiskDataSync;
|
||||
if (options & SDWebImageCacheMemoryOnly) cacheOptions |= SDImageCacheQueryMemoryOnly;
|
||||
if (options & SDWebImageQueryDataWhenInMemory) cacheOptions |= SDImageCacheQueryDataWhenInMemory;
|
||||
if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync;
|
||||
|
||||
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
|
||||
if (operation.isCancelled) {
|
||||
|
|
|
@ -71,15 +71,14 @@
|
|||
|
||||
- (void)sd_setAnimationImagesWithURLs:(nonnull NSArray<NSURL *> *)arrayOfURLs {
|
||||
[self sd_cancelCurrentAnimationImagesLoad];
|
||||
__weak __typeof(self)wself = self;
|
||||
|
||||
NSPointerArray *operationsArray = [self sd_animationOperationArray];
|
||||
|
||||
|
||||
[arrayOfURLs enumerateObjectsUsingBlock:^(NSURL *logoImageURL, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
__weak __typeof(self) wself = self;
|
||||
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;
|
||||
__strong typeof(wself) sself = wself;
|
||||
if (!sself) return;
|
||||
dispatch_main_async_safe(^{
|
||||
__strong UIImageView *sself = wself;
|
||||
[sself stopAnimating];
|
||||
if (sself && image) {
|
||||
NSMutableArray<UIImage *> *currentImages = [[sself animationImages] mutableCopy];
|
||||
|
|
|
@ -20,6 +20,10 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageInternalSetImageGroupKey;
|
|||
A SDWebImageManager instance to control the image download and cache process using in UIImageView+WebCache category and likes. If not provided, use the shared manager (SDWebImageManager)
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageExternalCustomManagerKey;
|
||||
/**
|
||||
The value specify that the image progress unit count cannot be determined because the progressBlock is not been called.
|
||||
*/
|
||||
FOUNDATION_EXPORT const int64_t SDWebImageProgressUnitCountUnknown; /* 1LL */
|
||||
|
||||
typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable imageData);
|
||||
|
||||
|
@ -28,11 +32,19 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima
|
|||
/**
|
||||
* Get the current image URL.
|
||||
*
|
||||
* Note that because of the limitations of categories this property can get out of sync
|
||||
* if you use setImage: directly.
|
||||
* @note Note that because of the limitations of categories this property can get out of sync if you use setImage: directly.
|
||||
*/
|
||||
- (nullable NSURL *)sd_imageURL;
|
||||
|
||||
/**
|
||||
* The current image loading progress associated to the view. The unit count is the received size and excepted size of download.
|
||||
* The `totalUnitCount` and `completedUnitCount` will be reset to 0 after a new image loading start (change from current queue). And they will be set to `SDWebImageProgressUnitCountUnknown` if the progressBlock not been called but the image loading success to mark the progress finished (change from main queue).
|
||||
* @note You can use Key-Value Observing on the progress, but you should take care that the change to progress is from a background queue during download(the same as progressBlock). If you want to using KVO and update the UI, make sure to dispatch on the main queue. And it's recommand to use some KVO libs like KVOController because it's more safe and easy to use.
|
||||
* @note The getter will create a progress instance if the value is nil. You can also set a custom progress instance and let it been updated during image loading
|
||||
* @note Note that because of the limitations of categories this property can get out of sync if you update the progress directly.
|
||||
*/
|
||||
@property (nonatomic, strong, null_resettable) NSProgress *sd_imageProgress;
|
||||
|
||||
/**
|
||||
* Set the imageView `image` with an `url` and optionally a placeholder image.
|
||||
*
|
||||
|
@ -88,7 +100,7 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima
|
|||
context:(nullable NSDictionary *)context;
|
||||
|
||||
/**
|
||||
* Cancel the current download
|
||||
* Cancel the current image load
|
||||
*/
|
||||
- (void)sd_cancelCurrentImageLoad;
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
NSString * const SDWebImageInternalSetImageGroupKey = @"internalSetImageGroup";
|
||||
NSString * const SDWebImageExternalCustomManagerKey = @"externalCustomManager";
|
||||
|
||||
const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
|
||||
|
||||
static char imageURLKey;
|
||||
|
||||
#if SD_UIKIT
|
||||
|
@ -30,6 +32,19 @@ static char TAG_ACTIVITY_SHOW;
|
|||
return objc_getAssociatedObject(self, &imageURLKey);
|
||||
}
|
||||
|
||||
- (NSProgress *)sd_imageProgress {
|
||||
NSProgress *progress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
|
||||
if (!progress) {
|
||||
progress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
|
||||
self.sd_imageProgress = progress;
|
||||
}
|
||||
return progress;
|
||||
}
|
||||
|
||||
- (void)setSd_imageProgress:(NSProgress *)sd_imageProgress {
|
||||
objc_setAssociatedObject(self, @selector(sd_imageProgress), sd_imageProgress, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
|
||||
placeholderImage:(nullable UIImage *)placeholder
|
||||
options:(SDWebImageOptions)options
|
||||
|
@ -68,6 +83,10 @@ static char TAG_ACTIVITY_SHOW;
|
|||
[self sd_addActivityIndicator];
|
||||
}
|
||||
|
||||
// reset the progress
|
||||
self.sd_imageProgress.totalUnitCount = 0;
|
||||
self.sd_imageProgress.completedUnitCount = 0;
|
||||
|
||||
SDWebImageManager *manager;
|
||||
if ([context valueForKey:SDWebImageExternalCustomManagerKey]) {
|
||||
manager = (SDWebImageManager *)[context valueForKey:SDWebImageExternalCustomManagerKey];
|
||||
|
@ -76,10 +95,22 @@ static char TAG_ACTIVITY_SHOW;
|
|||
}
|
||||
|
||||
__weak __typeof(self)wself = self;
|
||||
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
|
||||
SDWebImageDownloaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
|
||||
wself.sd_imageProgress.totalUnitCount = expectedSize;
|
||||
wself.sd_imageProgress.completedUnitCount = receivedSize;
|
||||
if (progressBlock) {
|
||||
progressBlock(receivedSize, expectedSize, targetURL);
|
||||
}
|
||||
};
|
||||
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
|
||||
__strong __typeof (wself) sself = wself;
|
||||
[sself sd_removeActivityIndicator];
|
||||
if (!sself) { return; }
|
||||
[sself sd_removeActivityIndicator];
|
||||
// if the progress not been updated, mark it to complete state
|
||||
if (finished && !error && sself.sd_imageProgress.totalUnitCount == 0 && sself.sd_imageProgress.completedUnitCount == 0) {
|
||||
sself.sd_imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
|
||||
sself.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
|
||||
}
|
||||
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
|
||||
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
|
||||
(!image && !(options & SDWebImageDelayPlaceholder)));
|
||||
|
|
|
@ -13,8 +13,9 @@
|
|||
#import <SDWebImage/MKAnnotationView+WebCache.h>
|
||||
#import <SDWebImage/UIButton+WebCache.h>
|
||||
#import <SDWebImage/FLAnimatedImageView+WebCache.h>
|
||||
#import <SDWebImage/UIView+WebCache.h>
|
||||
|
||||
@import FLAnimatedImage;
|
||||
static void * SDCategoriesTestsContext = &SDCategoriesTestsContext;
|
||||
|
||||
@interface SDCategoriesTests : SDTestCase
|
||||
|
||||
|
@ -139,4 +140,39 @@
|
|||
[self waitForExpectationsWithCommonTimeout];
|
||||
}
|
||||
|
||||
- (void)testUIViewImageProgressKVOWork {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"UIView imageProgressKVO failed"];
|
||||
UIView *view = [[UIView alloc] init];
|
||||
NSURL *originalImageURL = [NSURL URLWithString:kTestJpegURL];
|
||||
|
||||
[view.sd_imageProgress addObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) options:NSKeyValueObservingOptionNew context:SDCategoriesTestsContext];
|
||||
|
||||
// Clear the disk cache to force download from network
|
||||
[[SDImageCache sharedImageCache] removeImageForKey:kTestJpegURL withCompletion:^{
|
||||
[view sd_internalSetImageWithURL:originalImageURL placeholderImage:nil options:0 operationKey:nil setImageBlock:nil progress:nil completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
|
||||
expect(view.sd_imageProgress.fractionCompleted).equal(1.0);
|
||||
expect([view.sd_imageProgress.userInfo[NSStringFromSelector(_cmd)] boolValue]).equal(YES);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
}];
|
||||
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:^(NSError * _Nullable error) {
|
||||
[view.sd_imageProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) context:SDCategoriesTestsContext];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||
{
|
||||
if (context == SDCategoriesTestsContext) {
|
||||
if ([keyPath isEqualToString:NSStringFromSelector(@selector(fractionCompleted))]) {
|
||||
NSProgress *progress = object;
|
||||
NSNumber *completedValue = change[NSKeyValueChangeNewKey];
|
||||
expect(progress.fractionCompleted).equal(completedValue.doubleValue);
|
||||
// mark that KVO is called
|
||||
[progress setUserInfoObject:@(YES) forKey:NSStringFromSelector(@selector(testUIViewImageProgressKVOWork))];
|
||||
}
|
||||
} else {
|
||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#import <Expecta/Expecta.h>
|
||||
|
||||
FOUNDATION_EXPORT const int64_t kAsyncTestTimeout;
|
||||
FOUNDATION_EXPORT const int64_t kMinDelayNanosecond;
|
||||
FOUNDATION_EXPORT NSString * _Nonnull const kTestJpegURL;
|
||||
FOUNDATION_EXPORT NSString * _Nonnull const kTestPNGURL;
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#import "SDTestCase.h"
|
||||
|
||||
const int64_t kAsyncTestTimeout = 5;
|
||||
const int64_t kMinDelayNanosecond = NSEC_PER_MSEC * 100; // 0.1s
|
||||
NSString *const kTestJpegURL = @"http://via.placeholder.com/50x50.jpg";
|
||||
NSString *const kTestPNGURL = @"http://via.placeholder.com/50x50.png";
|
||||
|
||||
|
|
|
@ -186,7 +186,7 @@
|
|||
[[SDWebImageDownloader sharedDownloader] cancel:token];
|
||||
|
||||
// doesn't cancel immediately - since it uses dispatch async
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kMinDelayNanosecond), dispatch_get_main_queue(), ^{
|
||||
expect([SDWebImageDownloader sharedDownloader].currentDownloadCount).to.equal(0);
|
||||
[expectation fulfill];
|
||||
});
|
||||
|
|
|
@ -100,7 +100,7 @@
|
|||
[[SDWebImageManager sharedManager] cancelAll];
|
||||
|
||||
// doesn't cancel immediately - since it uses dispatch async
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kMinDelayNanosecond), dispatch_get_main_queue(), ^{
|
||||
expect([[SDWebImageManager sharedManager] isRunning]).to.equal(NO);
|
||||
[expectation fulfill];
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue