Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
* upstream/master:
  Prefetching file properties in the disk cleaning enumerator
  correct cachePolicy constants.
  Add (kinda) LIFO queue mode support for image downloading (fix SDWebImage#294)
  Add completion block support to `SDWebImagePrefetcher` (fix SDWebImage#127)
  Add `SDWebImageManager` delegate allowing fine control of manager's cache-in
  Add ability to set custom downloader HTTP headers (fix SDWebImage#171)
  Synchronize access to SDWebImageManager's mutable structures (fix SDWebImage#301)
  [BUG] Fix invalid alpha on JPEG files
  • Loading branch information
Reflejo committed Feb 22, 2013
2 parents f4447b7 + 05dd3f4 commit f62788f
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 19 deletions.
2 changes: 2 additions & 0 deletions Examples/SDWebImage Demo/MasterViewController.m
Expand Up @@ -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;
}

Expand Down
27 changes: 21 additions & 6 deletions SDWebImage/SDImageCache.m
Expand Up @@ -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];
}
}
});
Expand Down
26 changes: 26 additions & 0 deletions SDWebImage/SDWebImageDownloader.h
Expand Up @@ -16,6 +16,12 @@ typedef enum
SDWebImageDownloaderProgressiveDownload = 1 << 1
} SDWebImageDownloaderOptions;

typedef enum
{
SDWebImageDownloaderFILOQueueMode,
SDWebImageDownloaderLIFOQueueMode
} SDWebImageDownloaderQueueMode;

extern NSString *const SDWebImageDownloadStartNotification;
extern NSString *const SDWebImageDownloadStopNotification;

Expand All @@ -29,8 +35,28 @@ 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
*
Expand Down
31 changes: 29 additions & 2 deletions SDWebImage/SDWebImageDownloader.m
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
32 changes: 32 additions & 0 deletions SDWebImage/SDWebImageManager.h
Expand Up @@ -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).
Expand All @@ -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;

Expand Down
72 changes: 61 additions & 11 deletions SDWebImage/SDWebImageManager.m
Expand Up @@ -83,7 +83,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)
Expand All @@ -93,46 +96,93 @@ - (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)
{
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;
}

- (void)cancelAll
{
[self.runningOperations makeObjectsPerformSelector:@selector(cancel)];
[self.runningOperations removeAllObjects];
@synchronized(self.runningOperations)
{
[self.runningOperations makeObjectsPerformSelector:@selector(cancel)];
[self.runningOperations removeAllObjects];
}
}

- (BOOL)isRunning
Expand Down
9 changes: 9 additions & 0 deletions SDWebImage/SDWebImagePrefetcher.h
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions SDWebImage/SDWebImagePrefetcher.m
Expand Up @@ -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

Expand Down Expand Up @@ -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;
}
}
}];
}
Expand All @@ -90,10 +96,16 @@ - (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;
Expand Down

0 comments on commit f62788f

Please sign in to comment.