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:
commit
f2888f799c
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)]];
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue