Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge remote-tracking branch 'upstream/master'

* upstream/master:
  Detect parent operation cancellation in download operation completion block
  Prefetching file properties in the disk cleaning enumerator
  correct cachePolicy constants.
  Add (kinda) LIFO queue mode support for image downloading (fix #294)
  Add completion block support to `SDWebImagePrefetcher` (fix #127)
  Add `SDWebImageManager` delegate allowing fine control of manager's cache-in
  Add ability to set custom downloader HTTP headers (fix #171)
  Synchronize access to SDWebImageManager's mutable structures (fix #301)
  [BUG] Fix invalid alpha on JPEG files
  • Loading branch information...
commit 1f882834e8de00564d9234dc462e842299313400 2 parents f4447b7 + 95337c4
@Reflejo Reflejo authored
View
2  Examples/SDWebImage Demo/MasterViewController.m
@@ -332,6 +332,8 @@ - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
@"http://static2.dmcdn.net/static/video/269/938/51839962:jpeg_preview_small.jpg?20121105214014",
nil];
}
+ [SDWebImageManager.sharedManager.imageDownloader setValue:@"SDWebImage Demo" forHTTPHeaderField:@"AppName"];
+ SDWebImageManager.sharedManager.imageDownloader.queueMode = SDWebImageDownloaderLIFOQueueMode;
return self;
}
View
27 SDWebImage/SDImageCache.m
@@ -260,14 +260,29 @@ - (void)cleanDisk
dispatch_async(self.ioQueue, ^
{
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
- NSDirectoryEnumerator *fileEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:self.diskCachePath];
- for (NSString *fileName in fileEnumerator)
+ // convert NSString path to NSURL path
+ NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
+ // build an enumerator by also prefetching file properties we want to read
+ NSDirectoryEnumerator *fileEnumerator = [[NSFileManager defaultManager] enumeratorAtURL:diskCacheURL
+ includingPropertiesForKeys:@[ NSURLIsDirectoryKey, NSURLContentModificationDateKey ]
+ options:NSDirectoryEnumerationSkipsHiddenFiles
+ errorHandler:NULL];
+ for (NSURL *fileURL in fileEnumerator)
{
- NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
- NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
- if ([[[attrs fileModificationDate] laterDate:expirationDate] isEqualToDate:expirationDate])
+ // skip folder
+ NSNumber *isDirectory;
+ [fileURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL];
+ if ([isDirectory boolValue])
{
- [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
+ continue;
+ }
+
+ // compare file date with the max age
+ NSDate *fileModificationDate;
+ [fileURL getResourceValue:&fileModificationDate forKey:NSURLContentModificationDateKey error:NULL];
+ if ([[fileModificationDate laterDate:expirationDate] isEqualToDate:expirationDate])
+ {
+ [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
}
}
});
View
26 SDWebImage/SDWebImageDownloader.h
@@ -16,6 +16,12 @@ typedef enum
SDWebImageDownloaderProgressiveDownload = 1 << 1
} SDWebImageDownloaderOptions;
+typedef enum
+{
+ SDWebImageDownloaderFILOQueueMode,
+ SDWebImageDownloaderLIFOQueueMode
+} SDWebImageDownloaderQueueMode;
+
extern NSString *const SDWebImageDownloadStartNotification;
extern NSString *const SDWebImageDownloadStopNotification;
@@ -29,9 +35,29 @@ typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data,
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
+/**
+ * Changes download operations unqueue mode. Default value is `SDWebImageDownloaderFILOQueueMode`.
+ */
+@property (assign, nonatomic) SDWebImageDownloaderQueueMode queueMode;
+
+ (SDWebImageDownloader *)sharedDownloader;
/**
+ * Set a value for a HTTP header to be appended to each download HTTP request.
+ *
+ * @param value The value for the header field. Use `nil` value to remove the header.
+ * @param field The name of the header field to set.
+ */
+- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;
+
+/**
+ * Returns the value of the specified HTTP header field.
+ *
+ * @return The value associated with the header field field, or `nil` if there is no corresponding header field.
+ */
+- (NSString *)valueForHTTPHeaderField:(NSString *)field;
+
+/**
* Creates a SDWebImageDownloader async downloader instance with a given URL
*
* The delegate will be informed when the image is finish downloaded or an error has happen.
View
31 SDWebImage/SDWebImageDownloader.m
@@ -19,7 +19,9 @@
@interface SDWebImageDownloader ()
@property (strong, nonatomic) NSOperationQueue *downloadQueue;
+@property (weak, nonatomic) NSOperation *lastAddedOperation;
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
+@property (strong, nonatomic) NSMutableDictionary *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) dispatch_queue_t workingQueue;
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
@@ -65,9 +67,11 @@ - (id)init
{
if ((self = [super init]))
{
+ _queueMode = SDWebImageDownloaderFILOQueueMode;
_downloadQueue = NSOperationQueue.new;
_downloadQueue.maxConcurrentOperationCount = 2;
_URLCallbacks = NSMutableDictionary.new;
+ _HTTPHeaders = [NSMutableDictionary dictionaryWithObject:@"image/*" forKey:@"Accept"];
_workingQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloader", DISPATCH_QUEUE_SERIAL);
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
}
@@ -81,6 +85,23 @@ - (void)dealloc
SDDispatchQueueRelease(_barrierQueue);
}
+- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field
+{
+ if (value)
+ {
+ self.HTTPHeaders[field] = value;
+ }
+ else
+ {
+ [self.HTTPHeaders removeObjectForKey:field];
+ }
+}
+
+- (NSString *)valueForHTTPHeaderField:(NSString *)field
+{
+ return self.HTTPHeaders[field];
+}
+
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads
{
_downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
@@ -99,10 +120,10 @@ - (NSInteger)maxConcurrentDownloads
[self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^
{
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests
- NSMutableURLRequest *request = [NSMutableURLRequest.alloc initWithURL:url cachePolicy:NSURLCacheStorageNotAllowed timeoutInterval:15];
+ NSMutableURLRequest *request = [NSMutableURLRequest.alloc initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15];
request.HTTPShouldHandleCookies = NO;
request.HTTPShouldUsePipelining = YES;
- [request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
+ request.allHTTPHeaderFields = wself.HTTPHeaders;
operation = [SDWebImageDownloaderOperation.alloc initWithRequest:request queue:wself.workingQueue options:options progress:^(NSUInteger receivedSize, long long expectedSize)
{
if (!wself) return;
@@ -137,6 +158,12 @@ - (NSInteger)maxConcurrentDownloads
[sself removeCallbacksForURL:url];
}];
[wself.downloadQueue addOperation:operation];
+ if (wself.queueMode == SDWebImageDownloaderLIFOQueueMode)
+ {
+ // Emulate LIFO queue mode by systematically adding new operations as last operation's dependency
+ [wself.lastAddedOperation addDependency:operation];
+ wself.lastAddedOperation = operation;
+ }
}];
return operation;
View
32 SDWebImage/SDWebImageManager.h
@@ -38,6 +38,36 @@ typedef void(^SDWebImageCompletedBlock)(UIImage *image, NSError *error, SDImageC
typedef void(^SDWebImageCompletedWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished);
+@class SDWebImageManager;
+
+@protocol SDWebImageManagerDelegate <NSObject>
+
+@optional
+
+/**
+ * Controls which image should be downloaded when the image is not found in the cache.
+ *
+ * @param imageManager The current `SDWebImageManager`
+ * @param imageURL The url of the image to be downloaded
+ *
+ * @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied.
+ */
+- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
+
+/**
+ * Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory.
+ * NOTE: This method is called from a global queue in order to not to block the main thread.
+ *
+ * @param imageManager The current `SDWebImageManager`
+ * @param image The image to transform
+ * @param imageURL The url of the image to transform
+ *
+ * @return The transformed image object.
+ */
+- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
+
+@end
+
/**
* The SDWebImageManager is the class behind the UIImageView+WebCache category and likes.
* It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache).
@@ -61,6 +91,8 @@ typedef void(^SDWebImageCompletedWithFinishedBlock)(UIImage *image, NSError *err
*/
@interface SDWebImageManager : NSObject
+@property (weak, nonatomic) id<SDWebImageManagerDelegate> delegate;
+
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;
View
79 SDWebImage/SDWebImageManager.m
@@ -76,6 +76,7 @@ - (NSString *)cacheKeyForURL:(NSURL *)url
}
__block SDWebImageCombinedOperation *operation = SDWebImageCombinedOperation.new;
+ __weak SDWebImageCombinedOperation *weakOperation = operation;
if (!url || !completedBlock || (!(options & SDWebImageRetryFailed) && [self.failedURLs containsObject:url]))
{
@@ -83,7 +84,10 @@ - (NSString *)cacheKeyForURL:(NSURL *)url
return operation;
}
- [self.runningOperations addObject:operation];
+ @synchronized(self.runningOperations)
+ {
+ [self.runningOperations addObject:operation];
+ }
NSString *key = [self cacheKeyForURL:url];
[self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType)
@@ -93,37 +97,85 @@ - (NSString *)cacheKeyForURL:(NSURL *)url
if (image)
{
completedBlock(image, nil, cacheType, YES);
- [self.runningOperations removeObject:operation];
+ @synchronized(self.runningOperations)
+ {
+ [self.runningOperations removeObject:operation];
+ }
}
- else
+ else if (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])
{
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
__block id<SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished)
{
- completedBlock(downloadedImage, error, SDImageCacheTypeNone, finished);
-
- if (error)
+ if (weakOperation.cancelled)
{
+ completedBlock(nil, nil, SDImageCacheTypeNone, finished);
+ }
+ else if (error)
+ {
+ completedBlock(nil, error, SDImageCacheTypeNone, finished);
+
if (error.code != NSURLErrorNotConnectedToInternet)
{
- [self.failedURLs addObject:url];
+ @synchronized(self.failedURLs)
+ {
+ [self.failedURLs addObject:url];
+ }
}
}
- else if (downloadedImage && finished)
+ else
{
const BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
- [self.imageCache storeImage:downloadedImage imageData:data forKey:key toDisk:cacheOnDisk];
+
+ if (downloadedImage && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)])
+ {
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^
+ {
+ UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
+
+ dispatch_async(dispatch_get_main_queue(), ^
+ {
+ completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished);
+ });
+
+ if (transformedImage && finished)
+ {
+ [self.imageCache storeImage:transformedImage imageData:data forKey:key toDisk:cacheOnDisk];
+ }
+ });
+ }
+ else
+ {
+ completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished);
+
+ if (downloadedImage && finished)
+ {
+ [self.imageCache storeImage:downloadedImage imageData:data forKey:key toDisk:cacheOnDisk];
+ }
+ }
}
if (finished)
{
- [self.runningOperations removeObject:operation];
+ @synchronized(self.runningOperations)
+ {
+ [self.runningOperations removeObject:operation];
+ }
}
}];
operation.cancelBlock = ^{[subOperation cancel];};
}
+ else
+ {
+ // Image not in cache and download disallowed by delegate
+ completedBlock(nil, nil, SDImageCacheTypeNone, YES);
+ @synchronized(self.runningOperations)
+ {
+ [self.runningOperations removeObject:operation];
+ }
+ }
}];
return operation;
@@ -131,8 +183,11 @@ - (NSString *)cacheKeyForURL:(NSURL *)url
- (void)cancelAll
{
- [self.runningOperations makeObjectsPerformSelector:@selector(cancel)];
- [self.runningOperations removeAllObjects];
+ @synchronized(self.runningOperations)
+ {
+ [self.runningOperations makeObjectsPerformSelector:@selector(cancel)];
+ [self.runningOperations removeAllObjects];
+ }
}
- (BOOL)isRunning
View
9 SDWebImage/SDWebImagePrefetcher.h
@@ -39,6 +39,15 @@
*/
- (void)prefetchURLs:(NSArray *)urls;
+/**
+ * Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching,
+ * currently one image is downloaded at a time,
+ * and skips images for failed downloads and proceed to the next image in the list
+ *
+ * @param urls list of URLs to prefetch
+ * @param completionBlock block to be called when prefetching is completed
+ */
+- (void)prefetchURLs:(NSArray *)urls completed:(void (^)(NSUInteger finishedCount, NSUInteger skippedCount))completionBlock;
/**
* Remove and cancel queued list
View
12 SDWebImage/SDWebImagePrefetcher.m
@@ -17,6 +17,7 @@ @interface SDWebImagePrefetcher ()
@property (assign, nonatomic) NSUInteger skippedCount;
@property (assign, nonatomic) NSUInteger finishedCount;
@property (assign, nonatomic) NSTimeInterval startedTime;
+@property (SDDispatchQueueSetterSementics, nonatomic) void (^completionBlock)(NSUInteger, NSUInteger);
@end
@@ -79,6 +80,11 @@ - (void)startPrefetchingAtIndex:(NSUInteger)index
else if (self.finishedCount == self.requestedCount)
{
[self reportStatus];
+ if (self.completionBlock)
+ {
+ self.completionBlock(self.finishedCount, self.skippedCount);
+ self.completionBlock = nil;
+ }
}
}];
}
@@ -91,9 +97,15 @@ - (void)reportStatus
- (void)prefetchURLs:(NSArray *)urls
{
+ [self prefetchURLs:urls completed:nil];
+}
+
+- (void)prefetchURLs:(NSArray *)urls completed:(void (^)(NSUInteger, NSUInteger))completionBlock
+{
[self cancelPrefetching]; // Prevent duplicate prefetch request
self.startedTime = CFAbsoluteTimeGetCurrent();
self.prefetchURLs = urls;
+ self.completionBlock = completionBlock;
// Starts prefetching from the very first image on the list with the max allowed concurrency
NSUInteger listCount = self.prefetchURLs.count;
Please sign in to comment.
Something went wrong with that request. Please try again.