Merge branch 'master' of https://github.com/rs/SDWebImage into 5.x

* 'master' of https://github.com/rs/SDWebImage: (25 commits)
  When store image with no data for SDImageCache, check whether it contains alpha to use PNG or JPEG format
  Fix the way remove all elements from pointer array
  Remove some unused code, fix typo, update the comments
  Use a weak pointerArray to store the operations for sd_setAnimationImagesWithURLs, avoid extra retain of operation instance
  Use a copy-weak maptable for operations stored in UIView(WebCacheOperation) category to avoid retain of operation, and also use lock to keep thread-safe
  Fix progressive WebP decoding by creating data provider with actual data size
  Add a SDWebImageExternalCustomManagerKey context arguments to allow user to custom image manager for UIView category to avoid build from scratch
  Check the group instance before calling group leave
  Remove the out-dated compatible code for non-ARC
  Update the comments
  Allow custom SDWebImageDownloaderOperation to handle HTTP redirect
  Update the comments for that SDWebImageInternalSetImageGroupKey key
  Fix potential thread-safe problem in SDWebImagePrefetcher by keeping all access through prefetcher queue and retain the local URLs firstly
  Update the comments and remove the unnecessary main queue check
  Follow Apple doc and remove that manual calculation of byte alignment to make it more universal for different architecture device
  Change prefetchURLs from nonatomic to atomic to avoid multi-thread access crash
  capture currentURL instead of using index to access to avoid race condition
  Grab the poster image instead of image itself to avoid an UIAnimatedImage been set
  Update the documents
  Update tests to invalidate session after usage
  ...
This commit is contained in:
DreamPiggy 2017-12-22 21:06:48 +08:00
commit f2888f799c
20 changed files with 206 additions and 124 deletions

View File

@ -47,30 +47,38 @@
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
__weak typeof(self)weakSelf = self;
dispatch_group_t group = dispatch_group_create();
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:nil
setImageBlock:^(UIImage *image, NSData *imageData) {
// This setImageBlock may not called from main queue
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
FLAnimatedImage *animatedImage;
if (imageFormat == SDImageFormatGIF) {
animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData];
}
dispatch_main_async_safe(^{
if (animatedImage) {
weakSelf.animatedImage = animatedImage;
weakSelf.image = nil;
} else {
weakSelf.image = image;
weakSelf.animatedImage = nil;
// Firstly set the static poster image to avoid flashing
UIImage *posterImage = image.images ? image.images.firstObject : image;
weakSelf.image = posterImage;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// Secondly create FLAnimatedImage in global queue because it's time consuming, then set it back
FLAnimatedImage *animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData];
dispatch_main_async_safe(^{
weakSelf.animatedImage = animatedImage;
if (group) {
dispatch_group_leave(group);
}
});
});
} else {
weakSelf.image = image;
weakSelf.animatedImage = nil;
if (group) {
dispatch_group_leave(group);
}
});
}
}
progress:progressBlock
completed:completedBlock
context:@{SDWebImageInternalSetImageInGlobalQueueKey: @(YES)}];
context:group ? @{SDWebImageInternalSetImageGroupKey : group} : nil];
}
@end

View File

@ -50,7 +50,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
@property (strong, nonatomic, nonnull) NSCache *memCache;
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
@property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths;
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t ioQueue;
@property (strong, nonatomic, nullable) dispatch_queue_t ioQueue;
@end
@ -135,7 +135,6 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
SDDispatchQueueRelease(_ioQueue);
}
- (void)checkIfQueueIsIOQueue {
@ -225,8 +224,14 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
@autoreleasepool {
NSData *data = imageData;
if (!data && image) {
// If we do not have any data to detect image format, use PNG format
data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:SDImageFormatPNG];
// If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
SDImageFormat format;
if (SDCGImageRefContainsAlpha(image.CGImage)) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format];
}
[self storeImageDataToDisk:data forKey:key error:&writeError];
}

View File

@ -15,8 +15,8 @@
@interface SDWebImageCodersManager ()
@property (nonatomic, strong, nonnull) NSMutableArray<SDWebImageCoder>* mutableCoders;
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t mutableCodersAccessQueue;
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCoder>* mutableCoders;
@property (strong, nonatomic, nullable) dispatch_queue_t mutableCodersAccessQueue;
@end
@ -43,10 +43,6 @@
return self;
}
- (void)dealloc {
SDDispatchQueueRelease(_mutableCodersAccessQueue);
}
#pragma mark - Coder IO operations
- (void)addCoder:(nonnull id<SDWebImageCoder>)coder {

View File

@ -81,18 +81,6 @@
#define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type
#endif
#if OS_OBJECT_USE_OBJC
#undef SDDispatchQueueRelease
#undef SDDispatchQueueSetterSementics
#define SDDispatchQueueRelease(q)
#define SDDispatchQueueSetterSementics strong
#else
#undef SDDispatchQueueRelease
#undef SDDispatchQueueSetterSementics
#define SDDispatchQueueRelease(q) (dispatch_release(q))
#define SDDispatchQueueSetterSementics assign
#endif
FOUNDATION_EXPORT UIImage *SDScaledImageForKey(NSString *key, UIImage *image);
typedef void(^SDWebImageNoParamsBlock)(void);

View File

@ -10,7 +10,11 @@
#import "UIImage+MultiFormat.h"
#if !__has_feature(objc_arc)
#error SDWebImage is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
#error SDWebImage is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
#endif
#if !OS_OBJECT_USE_OBJC
#error SDWebImage need ARC for dispatch object
#endif
inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) {

View File

@ -162,7 +162,7 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
/**
* Creates an instance of a downloader with specified session configuration.
* *Note*: `timeoutIntervalForRequest` is going to be overwritten.
* @note `timeoutIntervalForRequest` is going to be overwritten.
* @return new instance of downloader class
*/
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;
@ -239,11 +239,18 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
/**
* 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.
* @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;
/**
* Invalidates the managed session, optionally canceling pending operations.
* @note If you use custom downloader instead of the shared downloader, you need call this method when you do not use it to avoid memory leak
* @param cancelPendingOperations Whether or not to cancel pending operations.
*/
- (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations;
@end

View File

@ -21,7 +21,7 @@
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;
@property (strong, nonatomic, nullable) dispatch_queue_t barrierQueue;
// The session in which data tasks will run
@property (strong, nonatomic) NSURLSession *session;
@ -107,12 +107,19 @@
delegateQueue:nil];
}
- (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations {
if (cancelPendingOperations) {
[self.session invalidateAndCancel];
} else {
[self.session finishTasksAndInvalidate];
}
}
- (void)dealloc {
[self.session invalidateAndCancel];
self.session = nil;
[self.downloadQueue cancelAllOperations];
SDDispatchQueueRelease(_barrierQueue);
}
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
@ -311,6 +318,7 @@ didReceiveResponse:(NSURLResponse *)response
#pragma mark NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
@ -319,7 +327,14 @@ didReceiveResponse:(NSURLResponse *)response
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
completionHandler(request);
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) {
[dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler];
} else {
completionHandler(request);
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {

View File

@ -38,7 +38,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
@property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;
@property (strong, nonatomic, nullable) dispatch_queue_t barrierQueue;
#if SD_UIKIT
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
@ -74,10 +74,6 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
return self;
}
- (void)dealloc {
SDDispatchQueueRelease(_barrierQueue);
}
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];

View File

@ -160,7 +160,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
if (partialImageRef) {
const size_t partialHeight = CGImageGetHeight(partialImageRef);
CGColorSpaceRef colorSpace = SDCGColorSpaceGetDeviceRGB();
CGContextRef bmContext = CGBitmapContextCreate(NULL, _width, _height, 8, _width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGContextRef bmContext = CGBitmapContextCreate(NULL, _width, _height, 8, 0, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
if (bmContext) {
CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = _width, .size.height = partialHeight}, partialImageRef);
CGImageRelease(partialImageRef);
@ -243,7 +243,6 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bytesPerRow = kBytesPerPixel * width;
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
@ -252,7 +251,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
width,
height,
kBitsPerComponent,
bytesPerRow,
0,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
if (context == NULL) {
@ -304,8 +303,6 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
// current color space
CGColorSpaceRef colorspaceRef = [[self class] colorSpaceForImageRef:sourceImageRef];
size_t bytesPerRow = kBytesPerPixel * destResolution.width;
// 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.
@ -313,7 +310,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
destResolution.width,
destResolution.height,
kBitsPerComponent,
bytesPerRow,
0,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);

View File

@ -66,6 +66,10 @@
}
}
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
return SDScaledImageForKey(key, image);
}
- (void)cachedImageExistsForURL:(nullable NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
NSString *key = [self cacheKeyForURL:url];
@ -201,6 +205,11 @@
}
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
// We've done the scale process in SDWebImageDownloader with the shared manager, this is used for custom manager and avoid extra scale.
if (self != [SDWebImageManager sharedManager] && self.cacheKeyFilter && downloadedImage) {
downloadedImage = [self scaledImageForKey:key image:downloadedImage];
}
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block

View File

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

View File

@ -11,7 +11,7 @@
@interface SDWebImagePrefetcher ()
@property (strong, nonatomic, nonnull) SDWebImageManager *manager;
@property (strong, nonatomic, nullable) NSArray<NSURL *> *prefetchURLs;
@property (strong, atomic, nullable) NSArray<NSURL *> *prefetchURLs; // may be accessed from different queue
@property (assign, nonatomic) NSUInteger requestedCount;
@property (assign, nonatomic) NSUInteger skippedCount;
@property (assign, nonatomic) NSUInteger finishedCount;
@ -55,33 +55,32 @@
}
- (void)startPrefetchingAtIndex:(NSUInteger)index {
if (index >= self.prefetchURLs.count) return;
self.requestedCount++;
[self.manager loadImageWithURL:self.prefetchURLs[index] options:self.options progress:nil completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
NSURL *currentURL;
@synchronized(self) {
if (index >= self.prefetchURLs.count) return;
currentURL = self.prefetchURLs[index];
self.requestedCount++;
}
[self.manager loadImageWithURL:currentURL options:self.options progress:nil completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!finished) return;
self.finishedCount++;
if (image) {
if (self.progressBlock) {
self.progressBlock(self.finishedCount,(self.prefetchURLs).count);
}
if (self.progressBlock) {
self.progressBlock(self.finishedCount,(self.prefetchURLs).count);
}
else {
if (self.progressBlock) {
self.progressBlock(self.finishedCount,(self.prefetchURLs).count);
}
if (!image) {
// Add last failed
self.skippedCount++;
}
if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)]) {
[self.delegate imagePrefetcher:self
didPrefetchURL:self.prefetchURLs[index]
didPrefetchURL:currentURL
finishedCount:self.finishedCount
totalCount:self.prefetchURLs.count
];
}
if (self.prefetchURLs.count > self.requestedCount) {
dispatch_async(self.prefetcherQueue, ^{
dispatch_queue_async_safe(self.prefetcherQueue, ^{
[self startPrefetchingAtIndex:self.requestedCount];
});
} else if (self.finishedCount == self.requestedCount) {
@ -132,10 +131,12 @@
}
- (void)cancelPrefetching {
self.prefetchURLs = nil;
self.skippedCount = 0;
self.requestedCount = 0;
self.finishedCount = 0;
@synchronized(self) {
self.prefetchURLs = nil;
self.skippedCount = 0;
self.requestedCount = 0;
self.finishedCount = 0;
}
[self.manager cancelAll];
}

View File

@ -169,8 +169,9 @@
// last_y may be 0, means no enough bitmap data to decode, ignore this
if (width + height > 0 && last_y > 0 && height >= last_y) {
// Construct a UIImage from the decoded RGBA value array
size_t rgbaSize = last_y * stride;
CGDataProviderRef provider =
CGDataProviderCreateWithData(NULL, rgba, 0, NULL);
CGDataProviderCreateWithData(NULL, rgba, rgbaSize, NULL);
CGColorSpaceRef colorSpaceRef = SDCGColorSpaceGetDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;

View File

@ -146,18 +146,10 @@ static inline NSString * backgroundImageURLKeyForState(UIControlState state) {
completed:completedBlock];
}
- (void)sd_setImageLoadOperation:(id<SDWebImageOperation>)operation forState:(UIControlState)state {
[self sd_setImageLoadOperation:operation forKey:[NSString stringWithFormat:@"UIButtonImageOperation%@", @(state)]];
}
- (void)sd_cancelImageLoadForState:(UIControlState)state {
[self sd_cancelImageLoadOperationWithKey:[NSString stringWithFormat:@"UIButtonImageOperation%@", @(state)]];
}
- (void)sd_setBackgroundImageLoadOperation:(id<SDWebImageOperation>)operation forState:(UIControlState)state {
[self sd_setImageLoadOperation:operation forKey:[NSString stringWithFormat:@"UIButtonBackgroundImageOperation%@", @(state)]];
}
- (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state {
[self sd_cancelImageLoadOperationWithKey:[NSString stringWithFormat:@"UIButtonBackgroundImageOperation%@", @(state)]];
}

View File

@ -73,10 +73,10 @@
[self sd_cancelCurrentAnimationImagesLoad];
__weak __typeof(self)wself = self;
NSMutableArray<id<SDWebImageOperation>> *operationsArray = [[NSMutableArray alloc] init];
NSPointerArray *operationsArray = [self sd_animationOperationArray];
[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) {
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(^{
__strong UIImageView *sself = wself;
@ -103,14 +103,40 @@
[sself startAnimating];
});
}];
[operationsArray addObject:operation];
@synchronized (self) {
[operationsArray addPointer:(__bridge void *)(operation)];
}
}];
}
[self sd_setImageLoadOperation:[operationsArray copy] forKey:@"UIImageViewAnimationImages"];
static char animationLoadOperationKey;
// element is weak because operation instance is retained by SDWebImageManager's runningOperations property
// we should use lock to keep thread-safe because these method may not be acessed from main queue
- (NSPointerArray *)sd_animationOperationArray {
@synchronized(self) {
NSPointerArray *operationsArray = objc_getAssociatedObject(self, &animationLoadOperationKey);
if (operationsArray) {
return operationsArray;
}
operationsArray = [NSPointerArray weakObjectsPointerArray];
objc_setAssociatedObject(self, &animationLoadOperationKey, operationsArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operationsArray;
}
}
- (void)sd_cancelCurrentAnimationImagesLoad {
[self sd_cancelImageLoadOperationWithKey:@"UIImageViewAnimationImages"];
NSPointerArray *operationsArray = [self sd_animationOperationArray];
if (operationsArray) {
@synchronized (self) {
for (id operation in operationsArray) {
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
[operation cancel];
}
}
operationsArray.count = 0;
}
}
}
#endif

View File

@ -12,7 +12,14 @@
#import "SDWebImageManager.h"
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageInternalSetImageInGlobalQueueKey;
/**
A Dispatch group to maintain setImageBlock and completionBlock. This key should be used only internally and may be changed in the future. (dispatch_group_t)
*/
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;
typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable imageData);

View File

@ -13,7 +13,8 @@
#import "objc/runtime.h"
#import "UIView+WebCacheOperation.h"
NSString * const SDWebImageInternalSetImageInGlobalQueueKey = @"setImageInGlobalQueue";
NSString * const SDWebImageInternalSetImageGroupKey = @"internalSetImageGroup";
NSString * const SDWebImageExternalCustomManagerKey = @"externalCustomManager";
static char imageURLKey;
@ -52,6 +53,10 @@ static char TAG_ACTIVITY_SHOW;
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (!(options & SDWebImageDelayPlaceholder)) {
if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
dispatch_group_enter(group);
}
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
@ -63,8 +68,15 @@ static char TAG_ACTIVITY_SHOW;
[self sd_addActivityIndicator];
}
SDWebImageManager *manager;
if ([context valueForKey:SDWebImageExternalCustomManagerKey]) {
manager = (SDWebImageManager *)[context valueForKey:SDWebImageExternalCustomManagerKey];
} else {
manager = [SDWebImageManager sharedManager];
}
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
[sself sd_removeActivityIndicator];
if (!sself) { return; }
@ -100,16 +112,23 @@ static char TAG_ACTIVITY_SHOW;
targetImage = placeholder;
targetData = nil;
}
BOOL shouldUseGlobalQueue = NO;
if (context && [context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey]) {
shouldUseGlobalQueue = [[context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey] boolValue];
}
dispatch_queue_t targetQueue = shouldUseGlobalQueue ? dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) : dispatch_get_main_queue();
dispatch_queue_async_safe(targetQueue, ^{
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
dispatch_main_async_safe(callCompletedBlockClojure);
});
if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
dispatch_group_enter(group);
dispatch_main_async_safe(^{
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
// ensure completion block is called after custom setImage process finish
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
callCompletedBlockClojure();
});
} else {
dispatch_main_async_safe(^{
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
callCompletedBlockClojure();
});
}
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {

View File

@ -12,15 +12,17 @@
#import "SDWebImageManager.h"
// These methods are used to support canceling for UIView image loading, it's designed to be used internal but not external.
// All the stored operations are weak, so it will be dalloced after image loading finished. If you need to store operations, use your own class to keep a strong reference for them.
@interface UIView (WebCacheOperation)
/**
* Set the image load operation (storage in a UIView based dictionary)
* Set the image load operation (storage in a UIView based weak map table)
*
* @param operation the operation
* @param key key for storing the operation
*/
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key;
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key;
/**
* Cancel all operations for the current UIView and key

View File

@ -14,52 +14,59 @@
static char loadOperationKey;
typedef NSMutableDictionary<NSString *, id> SDOperationsDictionary;
// key is copy, value is weak because operation instance is retained by SDWebImageManager's runningOperations property
// we should use lock to keep thread-safe because these method may not be acessed from main queue
typedef NSMapTable<NSString *, id<SDWebImageOperation>> SDOperationsDictionary;
@implementation UIView (WebCacheOperation)
- (SDOperationsDictionary *)operationDictionary {
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
if (operations) {
- (SDOperationsDictionary *)sd_operationDictionary {
@synchronized(self) {
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
if (operations) {
return operations;
}
operations = [[NSMapTable alloc] initWithKeyOptions:NSMapTableCopyIn valueOptions:NSMapTableWeakMemory capacity:0];
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
operations = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key {
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
if (key) {
[self sd_cancelImageLoadOperationWithKey:key];
if (operation) {
SDOperationsDictionary *operationDictionary = [self operationDictionary];
operationDictionary[key] = operation;
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
@synchronized (self) {
[operationDictionary setObject:operation forKey:key];
}
}
}
}
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self operationDictionary];
id operations = operationDictionary[key];
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id <SDWebImageOperation> operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
[(id<SDWebImageOperation>) operations cancel];
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
id<SDWebImageOperation> operation;
@synchronized (self) {
operation = [operationDictionary objectForKey:key];
}
if (operation) {
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]){
[operation cancel];
}
@synchronized (self) {
[operationDictionary removeObjectForKey:key];
}
[operationDictionary removeObjectForKey:key];
}
}
- (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key {
if (key) {
SDOperationsDictionary *operationDictionary = [self operationDictionary];
[operationDictionary removeObjectForKey:key];
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
@synchronized (self) {
[operationDictionary removeObjectForKey:key];
}
}
}

View File

@ -61,6 +61,7 @@
- (void)test01ThatSharedDownloaderIsNotEqualToInitDownloader {
SDWebImageDownloader *downloader = [[SDWebImageDownloader alloc] init];
expect(downloader).toNot.equal([SDWebImageDownloader sharedDownloader]);
[downloader invalidateSessionAndCancel:YES];
}
- (void)test02ThatByDefaultDownloaderSetsTheAcceptHTTPHeader {
@ -377,6 +378,7 @@
}];
[self waitForExpectationsWithCommonTimeout];
[downloader invalidateSessionAndCancel:YES];
}
@end