Authored by Olivier Poitrey

Merge pull request #996 from harishkashyap/fix-memory-issues

Adds option to decompress images and select prefetcher Queue
@@ -37,6 +37,12 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot @@ -37,6 +37,12 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot
37 @interface SDImageCache : NSObject 37 @interface SDImageCache : NSObject
38 38
39 /** 39 /**
  40 + * Decompressing images that are downloaded and cached can improve peformance but can consume lot of memory.
  41 + * Defaults to YES. Set this to NO if you are experiencing a crash due to excessive memory consumption.
  42 + */
  43 +@property (assign, nonatomic) BOOL shouldDecompressImages;
  44 +
  45 +/**
40 * The maximum "total cost" of the in-memory image cache. The cost function is the number of pixels held in memory. 46 * The maximum "total cost" of the in-memory image cache. The cost function is the number of pixels held in memory.
41 */ 47 */
42 @property (assign, nonatomic) NSUInteger maxMemoryCost; 48 @property (assign, nonatomic) NSUInteger maxMemoryCost;
@@ -77,6 +77,9 @@ BOOL ImageDataHasPNGPreffix(NSData *data) { @@ -77,6 +77,9 @@ BOOL ImageDataHasPNGPreffix(NSData *data) {
77 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 77 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
78 _diskCachePath = [paths[0] stringByAppendingPathComponent:fullNamespace]; 78 _diskCachePath = [paths[0] stringByAppendingPathComponent:fullNamespace];
79 79
  80 + // Set decompression to YES
  81 + _shouldDecompressImages = YES;
  82 +
80 dispatch_sync(_ioQueue, ^{ 83 dispatch_sync(_ioQueue, ^{
81 _fileManager = [NSFileManager new]; 84 _fileManager = [NSFileManager new];
82 }); 85 });
@@ -266,7 +269,9 @@ BOOL ImageDataHasPNGPreffix(NSData *data) { @@ -266,7 +269,9 @@ BOOL ImageDataHasPNGPreffix(NSData *data) {
266 if (data) { 269 if (data) {
267 UIImage *image = [UIImage sd_imageWithData:data]; 270 UIImage *image = [UIImage sd_imageWithData:data];
268 image = [self scaledImageForKey:key image:image]; 271 image = [self scaledImageForKey:key image:image];
  272 + if (self.shouldDecompressImages) {
269 image = [UIImage decodedImageWithImage:image]; 273 image = [UIImage decodedImageWithImage:image];
  274 + }
270 return image; 275 return image;
271 } 276 }
272 else { 277 else {
@@ -49,8 +49,6 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) { @@ -49,8 +49,6 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
49 * Put the image in the high priority queue. 49 * Put the image in the high priority queue.
50 */ 50 */
51 SDWebImageDownloaderHighPriority = 1 << 7, 51 SDWebImageDownloaderHighPriority = 1 << 7,
52 -  
53 -  
54 }; 52 };
55 53
56 typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) { 54 typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
@@ -79,12 +77,17 @@ typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDi @@ -79,12 +77,17 @@ typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDi
79 */ 77 */
80 @interface SDWebImageDownloader : NSObject 78 @interface SDWebImageDownloader : NSObject
81 79
  80 +/**
  81 + * Decompressing images that are downloaded and cached can improve peformance but can consume lot of memory.
  82 + * Defaults to YES. Set this to NO if you are experiencing a crash due to excessive memory consumption.
  83 + */
  84 +@property (assign, nonatomic) BOOL shouldDecompressImages;
  85 +
82 @property (assign, nonatomic) NSInteger maxConcurrentDownloads; 86 @property (assign, nonatomic) NSInteger maxConcurrentDownloads;
83 87
84 /** 88 /**
85 * Shows the current amount of downloads that still need to be downloaded 89 * Shows the current amount of downloads that still need to be downloaded
86 */ 90 */
87 -  
88 @property (readonly, nonatomic) NSUInteger currentDownloadCount; 91 @property (readonly, nonatomic) NSUInteger currentDownloadCount;
89 92
90 93
@@ -65,6 +65,7 @@ static NSString *const kCompletedCallbackKey = @"completed"; @@ -65,6 +65,7 @@ static NSString *const kCompletedCallbackKey = @"completed";
65 - (id)init { 65 - (id)init {
66 if ((self = [super init])) { 66 if ((self = [super init])) {
67 _operationClass = [SDWebImageDownloaderOperation class]; 67 _operationClass = [SDWebImageDownloaderOperation class];
  68 + _shouldDecompressImages = YES;
68 _executionOrder = SDWebImageDownloaderFIFOExecutionOrder; 69 _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
69 _downloadQueue = [NSOperationQueue new]; 70 _downloadQueue = [NSOperationQueue new];
70 _downloadQueue.maxConcurrentOperationCount = 6; 71 _downloadQueue.maxConcurrentOperationCount = 6;
@@ -166,6 +167,7 @@ static NSString *const kCompletedCallbackKey = @"completed"; @@ -166,6 +167,7 @@ static NSString *const kCompletedCallbackKey = @"completed";
166 [sself.URLCallbacks removeObjectForKey:url]; 167 [sself.URLCallbacks removeObjectForKey:url];
167 }); 168 });
168 }]; 169 }];
  170 + operation.shouldDecompressImages = wself.shouldDecompressImages;
169 171
170 if (wself.username && wself.password) { 172 if (wself.username && wself.password) {
171 operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession]; 173 operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
@@ -17,6 +17,9 @@ @@ -17,6 +17,9 @@
17 */ 17 */
18 @property (strong, nonatomic, readonly) NSURLRequest *request; 18 @property (strong, nonatomic, readonly) NSURLRequest *request;
19 19
  20 +
  21 +@property (assign, nonatomic) BOOL shouldDecompressImages;
  22 +
20 /** 23 /**
21 * Whether the URL connection should consult the credential storage for authenticating the connection. `YES` by default. 24 * Whether the URL connection should consult the credential storage for authenticating the connection. `YES` by default.
22 * 25 *
@@ -47,6 +47,7 @@ @@ -47,6 +47,7 @@
47 cancelled:(SDWebImageNoParamsBlock)cancelBlock { 47 cancelled:(SDWebImageNoParamsBlock)cancelBlock {
48 if ((self = [super init])) { 48 if ((self = [super init])) {
49 _request = request; 49 _request = request;
  50 + _shouldDecompressImages = YES;
50 _shouldUseCredentialStorage = YES; 51 _shouldUseCredentialStorage = YES;
51 _options = options; 52 _options = options;
52 _progressBlock = [progressBlock copy]; 53 _progressBlock = [progressBlock copy];
@@ -294,7 +295,12 @@ @@ -294,7 +295,12 @@
294 UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation]; 295 UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];
295 NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]; 296 NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
296 UIImage *scaledImage = [self scaledImageForKey:key image:image]; 297 UIImage *scaledImage = [self scaledImageForKey:key image:image];
  298 + if (self.shouldDecompressImages) {
297 image = [UIImage decodedImageWithImage:scaledImage]; 299 image = [UIImage decodedImageWithImage:scaledImage];
  300 + }
  301 + else {
  302 + image = scaledImage;
  303 + }
298 CGImageRelease(partialImageRef); 304 CGImageRelease(partialImageRef);
299 dispatch_main_sync_safe(^{ 305 dispatch_main_sync_safe(^{
300 if (self.completedBlock) { 306 if (self.completedBlock) {
@@ -365,8 +371,10 @@ @@ -365,8 +371,10 @@
365 371
366 // Do not force decoding animated GIFs 372 // Do not force decoding animated GIFs
367 if (!image.images) { 373 if (!image.images) {
  374 + if (self.shouldDecompressImages) {
368 image = [UIImage decodedImageWithImage:image]; 375 image = [UIImage decodedImageWithImage:image];
369 } 376 }
  377 + }
370 if (CGSizeEqualToSize(image.size, CGSizeZero)) { 378 if (CGSizeEqualToSize(image.size, CGSizeZero)) {
371 completionBlock(nil, nil, [NSError errorWithDomain:@"SDWebImageErrorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES); 379 completionBlock(nil, nil, [NSError errorWithDomain:@"SDWebImageErrorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
372 } 380 }
@@ -58,6 +58,11 @@ typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls, @@ -58,6 +58,11 @@ typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls,
58 */ 58 */
59 @property (nonatomic, assign) SDWebImageOptions options; 59 @property (nonatomic, assign) SDWebImageOptions options;
60 60
  61 +/**
  62 + * Queue options for Prefetcher. Defaults to Main Queue.
  63 + */
  64 +@property (nonatomic, assign) dispatch_queue_t prefetcherQueue;
  65 +
61 @property (weak, nonatomic) id <SDWebImagePrefetcherDelegate> delegate; 66 @property (weak, nonatomic) id <SDWebImagePrefetcherDelegate> delegate;
62 67
63 /** 68 /**
@@ -40,6 +40,7 @@ @@ -40,6 +40,7 @@
40 if ((self = [super init])) { 40 if ((self = [super init])) {
41 _manager = [SDWebImageManager new]; 41 _manager = [SDWebImageManager new];
42 _options = SDWebImageLowPriority; 42 _options = SDWebImageLowPriority;
  43 + _prefetcherQueue = dispatch_get_main_queue();
43 self.maxConcurrentDownloads = 3; 44 self.maxConcurrentDownloads = 3;
44 } 45 }
45 return self; 46 return self;
@@ -82,9 +83,8 @@ @@ -82,9 +83,8 @@
82 totalCount:self.prefetchURLs.count 83 totalCount:self.prefetchURLs.count
83 ]; 84 ];
84 } 85 }
85 -  
86 if (self.prefetchURLs.count > self.requestedCount) { 86 if (self.prefetchURLs.count > self.requestedCount) {
87 - dispatch_async(dispatch_get_main_queue(), ^{ 87 + dispatch_async(self.prefetcherQueue, ^{
88 [self startPrefetchingAtIndex:self.requestedCount]; 88 [self startPrefetchingAtIndex:self.requestedCount];
89 }); 89 });
90 } 90 }