Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Update to latest SDWebImage.

  • Loading branch information...
commit 3e84e0f0ed6c5fd444ed8804f9c9736b0276655f 1 parent b0cef85
Henrik Nyh authored kirbyt committed
9 src/Sample/Classes/SDWebImageSample/KTPhotoView+SDWebImage.m
View
@@ -20,7 +20,10 @@ - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder {
// Remove in progress downloader from queue
[manager cancelForDelegate:self];
- UIImage *cachedImage = [manager imageWithURL:url];
+ UIImage *cachedImage = nil;
+ if (url) {
+ cachedImage = [manager imageWithURL:url];
+ }
if (cachedImage) {
[self setImage:cachedImage];
@@ -30,7 +33,9 @@ - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder {
[self setImage:placeholder];
}
- [manager downloadWithURL:url delegate:self];
+ if (url) {
+ [manager downloadWithURL:url delegate:self];
+ }
}
}
9 src/Sample/Classes/SDWebImageSample/KTThumbView+SDWebImage.m
View
@@ -20,7 +20,10 @@ - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder {
// Remove in progress downloader from queue
[manager cancelForDelegate:self];
- UIImage *cachedImage = [manager imageWithURL:url];
+ UIImage *cachedImage = nil;
+ if (url) {
+ cachedImage = [manager imageWithURL:url];
+ }
if (cachedImage) {
[self setThumbImage:cachedImage];
@@ -30,7 +33,9 @@ - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder {
[self setThumbImage:placeholder];
}
- [manager downloadWithURL:url delegate:self];
+ if (url) {
+ [manager downloadWithURL:url delegate:self];
+ }
}
}
51 src/Sample/Classes/SDWebImageSample/SDWebImage/README.md
View
@@ -6,7 +6,7 @@ This library provides a category for UIImageVIew with support for remote images
It provides:
- An UIImageView category adding web image and cache management to the Cocoa Touch framework
-- An asynchronous image downloader using threads (NSOperation)
+- An asynchronous image downloader
- An asynchronous memory + disk image caching with automatic cache expiration handling
- A garantie that the same URL won't be downloaded several times
- A garantie that bogus URLs won't be retried again and again
@@ -39,23 +39,21 @@ time faster than my own servers... WTF??
In fact, my servers were well but a lot of latency was added to the requests, certainly because my
application wasn't responsive enough to handle the requests at full speed. At this moment, I
-understood something important, asynchronous NSURLConnections are tied to the main runloop (I
-guess). It's certainly based on the poll multiplexer system call, which allows a single thread to
-handle quite a huge number of simultaneous connections. It works well while nothing blocks in the
-loop, but in this loop, there is also the events handling. A simple test to recognize an application
-using NSURLConnection to load there remote images is to scroll the UITableView with your finger to
-disclose an unloaded image, and to keep your finger pressed on the screen. If the image doesn't load
-until you release you finger, the application is certainly using NSURLConnection (try with the
-Facebook app for instance). I'm not completely clear about the reason of this blocking, I thought
-the iPhone was running a dedicated run-loop for connections, but the fact is, NSURLConnection is
-affected by the application events and/or UI handling (or something else I'm not aware of).
-
-Thus I explored another path and found this marvelous NSOperation class to handle concurrency with
-love. I ran some quick tests with this tool and I instantly got enhanced responsiveness of the image
-loading in my UITableView by... a lot. Thus I rewrote the [Fraggle][]'s implementation using the
-same concept of drop-in remplacement for UIImageView but with this new technic. I thought the result
-could benefits to a lot of other applications, thus we decided, with [Fraggle][], to open-sourced
-it, et voila!
+understood something important, asynchronous NSURLConnections are tied to the main runloop in the
+NSEventTrackingRunLoopMode. As explained in the documentation, this runloop mode is affected by
+UI events:
+
+> Cocoa uses this mode to restrict incoming events during mouse-dragging loops and other sorts of
+> user interface tracking loops.
+
+A simple test to recognize an application using NSURLConnection in its default mode to load there
+remote images is to scroll the UITableView with your finger to disclose an unloaded image, and to
+keep your finger pressed on the screen. If the image doesn't load until you release you finger,
+you've got one (try with the Facebook app for instance). It took me quite some time to understand
+the reason for this lagging issue. Actually I first used NSOperation to workaround this issue.
+
+This technic combined with an image cache instantly gave a lot of responsiveness to my app.
+I thought this lib could benefits to a lot of other Cocoa Touch application so I open-sourced it.
How To Use It
-------------
@@ -64,7 +62,7 @@ How To Use It
Just #import the UIImageView+WebCache.h header, and call the setImageWithURL:placeholderImage:
method from the tableView:cellForRowAtIndexPath: UITableViewDataSource method. Everything will be
-handled for you, from parallel downloads to caching management.
+handled for you, from async downloads to caching management.
#import "UIImageView+WebCache.h"
@@ -113,23 +111,22 @@ Here is a simple example of how to use SDWebImageManager:
}
Your class will have to implement the SDWebImageManagerDelegate protocol, and to implement the
-imageHelper:didFinishWithImage: method from this protocol:
+webImageManager:didFinishWithImage: method from this protocol:
- - (void)imageHelper:(SDWebImageManager *)imageHelper didFinishWithImage:(UIImage *)image
+ - (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)image
{
// Do something with the downloaded image
}
### Using Asynchronous Image Downloader Independently
-It is possible to use the NSOperation based image downloader independently. Just create an instance
-of SDWebImageDownloader using its convenience constructor downloaderWithURL:target:action:.
+It is possible to use the async image downloader independently. You just have to create an instance
+of SDWebImageDownloader using its convenience constructor downloaderWithURL:delegate:.
downloader = [SDWebImageDownloader downloaderWithURL:url delegate:self];
-The download will by queued immediately and the imageDownloader:didFinishWithImage: method from the
-SDWebImageDownloaderDelegate protocol will be called as soon as the download of image is completed
-(prepare not to be called from the main thread).
+The download will start immediately and the imageDownloader:didFinishWithImage: method from the
+SDWebImageDownloaderDelegate protocol will be called as soon as the download of image is completed.
### Using Asynchronous Image Caching Independently
@@ -168,4 +165,4 @@ Future Enhancements
[Fraggle]: http://fraggle.squarespace.com
[Urban Rivals]: http://fraggle.squarespace.com/blog/2009/9/15/almost-done-here-is-urban-rivals-iphone-trailer.html
[Three20]: http://groups.google.com/group/three20
-[Joe Hewitt]: http://www.joehewitt.com
+[Joe Hewitt]: http://www.joehewitt.com
72 src/Sample/Classes/SDWebImageSample/SDWebImage/SDImageCache.m
View
@@ -4,7 +4,7 @@
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
- */
+ */
#import "SDImageCache.h"
#import <CommonCrypto/CommonDigest.h>
@@ -15,18 +15,6 @@
@implementation SDImageCache
-#pragma mark SDImageCache (notification handlers)
-
-- (void)didReceiveMemoryWarning:(void *)object
-{
- [self clearMemory];
-}
-
-- (void)willTerminate
-{
- [self cleanDisk];
-}
-
#pragma mark NSObject
- (id)init
@@ -39,10 +27,13 @@ - (id)init
// Init the disk cache
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
diskCachePath = [[[paths objectAtIndex:0] stringByAppendingPathComponent:@"ImageCache"] retain];
-
+
if (![[NSFileManager defaultManager] fileExistsAtPath:diskCachePath])
{
- [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath attributes:nil];
+ [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:NULL];
}
// Init the operation queue
@@ -51,14 +42,26 @@ - (id)init
// Subscribe to app events
[[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(didReceiveMemoryWarning:)
- name:UIApplicationDidReceiveMemoryWarningNotification
+ selector:@selector(clearMemory)
+ name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
-
+
[[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(willTerminate)
- name:UIApplicationWillTerminateNotification
- object:nil];
+ selector:@selector(cleanDisk)
+ name:UIApplicationWillTerminateNotification
+ object:nil];
+
+ #ifdef __IPHONE_4_0
+ UIDevice *device = [UIDevice currentDevice];
+ if ([device respondsToSelector:@selector(isMultitaskingSupported)] && device.multitaskingSupported)
+ {
+ // When in background, clean memory in order to have less chance to be killed
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(clearMemory)
+ name:UIApplicationDidEnterBackgroundNotification
+ object:nil];
+ }
+ #endif
}
return self;
@@ -66,18 +69,12 @@ - (id)init
- (void)dealloc
{
- [memCache release];
- [diskCachePath release];
- [cacheInQueue release];
-
- [[NSNotificationCenter defaultCenter] removeObserver:self
- name:UIApplicationDidReceiveMemoryWarningNotification
- object:nil];
-
- [[NSNotificationCenter defaultCenter] removeObserver:self
- name:UIApplicationWillTerminateNotification
- object:nil];
-
+ [memCache release], memCache = nil;
+ [diskCachePath release], diskCachePath = nil;
+ [cacheInQueue release], cacheInQueue = nil;
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
[super dealloc];
}
@@ -89,7 +86,7 @@ + (SDImageCache *)sharedImageCache
{
instance = [[SDImageCache alloc] init];
}
-
+
return instance;
}
@@ -134,7 +131,7 @@ - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk
[memCache setObject:image forKey:key];
if (toDisk)
- {
+ {
[cacheInQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(storeKeyToDisk:) object:key] autorelease]];
}
}
@@ -187,7 +184,10 @@ - (void)clearDisk
{
[cacheInQueue cancelAllOperations];
[[NSFileManager defaultManager] removeItemAtPath:diskCachePath error:nil];
- [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath attributes:nil];
+ [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:NULL];
}
- (void)cleanDisk
15 src/Sample/Classes/SDWebImageSample/SDWebImage/SDWebImageDownloader.h
View
@@ -9,16 +9,23 @@
#import <Foundation/Foundation.h>
#import "SDWebImageDownloaderDelegate.h"
-@interface SDWebImageDownloader : NSOperation
+@interface SDWebImageDownloader : NSObject
{
+ @private
NSURL *url;
id<SDWebImageDownloaderDelegate> delegate;
+ NSURLConnection *connection;
+ NSMutableData *imageData;
}
-@property (retain) NSURL *url;
-@property (assign) id<SDWebImageDownloaderDelegate> delegate;
+@property (nonatomic, retain) NSURL *url;
+@property (nonatomic, assign) id<SDWebImageDownloaderDelegate> delegate;
+ (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate;
-+ (void)setMaxConcurrentDownloads:(NSUInteger)max;
+- (void)start;
+- (void)cancel;
+
+// This method is now no-op and is deprecated
++ (void)setMaxConcurrentDownloads:(NSUInteger)max __attribute__((deprecated));
@end
98 src/Sample/Classes/SDWebImageSample/SDWebImage/SDWebImageDownloader.m
View
@@ -8,59 +8,103 @@
#import "SDWebImageDownloader.h"
-static NSOperationQueue *downloadQueue;
+@interface SDWebImageDownloader ()
+@property (nonatomic, retain) NSURLConnection *connection;
+@property (nonatomic, retain) NSMutableData *imageData;
+@end
@implementation SDWebImageDownloader
+@synthesize url, delegate, connection, imageData;
-@synthesize url, delegate;
-
-- (void)dealloc
-{
- [url release];
- [super dealloc];
-}
+#pragma mark Public Methods
+ (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate
{
SDWebImageDownloader *downloader = [[[SDWebImageDownloader alloc] init] autorelease];
downloader.url = url;
downloader.delegate = delegate;
+ [downloader performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:YES];
+ return downloader;
+}
- if (downloadQueue == nil)
++ (void)setMaxConcurrentDownloads:(NSUInteger)max
+{
+ // NOOP
+}
+
+- (void)start
+{
+ // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests
+ NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15];
+ self.connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO] autorelease];
+ // Ensure we aren't blocked by UI manipulations (default runloop mode for NSURLConnection is NSEventTrackingRunLoopMode)
+ [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
+ [connection start];
+ [request release];
+
+ if (connection)
{
- downloadQueue = [[NSOperationQueue alloc] init];
- downloadQueue.maxConcurrentOperationCount = 8;
+ self.imageData = [NSMutableData data];
+ }
+ else
+ {
+ if ([delegate respondsToSelector:@selector(imageDownloader:didFailWithError:)])
+ {
+ [delegate performSelector:@selector(imageDownloader:didFailWithError:) withObject:self withObject:nil];
+ }
}
-
- [downloadQueue addOperation:downloader];
-
- return downloader;
}
-+ (void)setMaxConcurrentDownloads:(NSUInteger)max
+- (void)cancel
{
- if (downloadQueue == nil)
+ if (connection)
{
- downloadQueue = [[NSOperationQueue alloc] init];
+ [connection cancel];
+ self.connection = nil;
}
+}
+
+#pragma mark NSURLConnection (delegate)
- downloadQueue.maxConcurrentOperationCount = max;
+- (void)connection:(NSURLConnection *)aConnection didReceiveData:(NSData *)data
+{
+ [imageData appendData:data];
}
-- (void)main
+- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection
{
- NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+ UIImage *image = [[UIImage alloc] initWithData:imageData];
+ self.imageData = nil;
+ self.connection = nil;
- // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests
- NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:5];
- UIImage *image = [UIImage imageWithData:[NSURLConnection sendSynchronousRequest:request returningResponse:nil error:NULL]];
-
- if (!self.isCancelled && [delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:)])
+ if ([delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:)])
{
[delegate performSelector:@selector(imageDownloader:didFinishWithImage:) withObject:self withObject:image];
}
- [pool release];
+ [image release];
+}
+
+- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
+{
+ if ([delegate respondsToSelector:@selector(imageDownloader:didFailWithError:)])
+ {
+ [delegate performSelector:@selector(imageDownloader:didFailWithError:) withObject:self withObject:error];
+ }
+
+ self.connection = nil;
+ self.imageData = nil;
}
+#pragma mark NSObject
+
+- (void)dealloc
+{
+ [url release], url = nil;
+ [connection release], connection = nil;
+ [imageData release], imageData = nil;
+ [super dealloc];
+}
+
+
@end
3  src/Sample/Classes/SDWebImageSample/SDWebImage/SDWebImageDownloaderDelegate.h
View
@@ -13,5 +13,6 @@
@optional
- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image;
+- (void)imageDownloader:(SDWebImageDownloader *)downloader didFailWithError:(NSError *)error;
-@end
+@end
2  src/Sample/Classes/SDWebImageSample/SDWebImage/SDWebImageManager.h
View
@@ -4,7 +4,7 @@
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
- */
+ */
#import <UIKit/UIKit.h>
#import "SDWebImageDownloaderDelegate.h"
77 src/Sample/Classes/SDWebImageSample/SDWebImage/SDWebImageManager.m
View
@@ -4,7 +4,7 @@
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
- */
+ */
#import "SDWebImageManager.h"
#import "SDImageCache.h"
@@ -26,12 +26,12 @@ - (id)init
return self;
}
-- (void) dealloc
+- (void)dealloc
{
- [delegates release];
- [downloaders release];
- [downloaderForURL release];
- [failedURLs release];
+ [delegates release], delegates = nil;
+ [downloaders release], downloaders = nil;
+ [downloaderForURL release], downloaderForURL = nil;
+ [failedURLs release], failedURLs = nil;
[super dealloc];
}
@@ -67,62 +67,53 @@ - (void)downloadWithURL:(NSURL *)url delegate:(id<SDWebImageManagerDelegate>)del
[downloaderForURL setObject:downloader forKey:url];
}
- @synchronized(self)
- {
- [delegates addObject:delegate];
- [downloaders addObject:downloader];
- }
+ [delegates addObject:delegate];
+ [downloaders addObject:downloader];
}
- (void)cancelForDelegate:(id<SDWebImageManagerDelegate>)delegate
{
- @synchronized(self)
+ NSUInteger idx = [delegates indexOfObjectIdenticalTo:delegate];
+
+ if (idx == NSNotFound)
{
- NSUInteger idx = [delegates indexOfObjectIdenticalTo:delegate];
+ return;
+ }
- if (idx == NSNotFound)
- {
- return;
- }
+ SDWebImageDownloader *downloader = [[downloaders objectAtIndex:idx] retain];
- SDWebImageDownloader *downloader = [[downloaders objectAtIndex:idx] retain];
-
- [delegates removeObjectAtIndex:idx];
- [downloaders removeObjectAtIndex:idx];
+ [delegates removeObjectAtIndex:idx];
+ [downloaders removeObjectAtIndex:idx];
- if (![downloaders containsObject:downloader])
- {
- // No more delegate are waiting for this download, cancel it
- [downloader cancel];
- [downloaderForURL removeObjectForKey:downloader.url];
- }
-
- [downloader release];
+ if (![downloaders containsObject:downloader])
+ {
+ // No more delegate are waiting for this download, cancel it
+ [downloader cancel];
+ [downloaderForURL removeObjectForKey:downloader.url];
}
+
+ [downloader release];
}
- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image
{
[downloader retain];
- @synchronized(self)
+ // Notify all the delegates with this downloader
+ for (NSInteger idx = [downloaders count] - 1; idx >= 0; idx--)
{
- // Notify all the delegates with this downloader
- for (NSInteger idx = [downloaders count] - 1; idx >= 0; idx--)
+ SDWebImageDownloader *aDownloader = [downloaders objectAtIndex:idx];
+ if (aDownloader == downloader)
{
- SDWebImageDownloader *aDownloader = [downloaders objectAtIndex:idx];
- if (aDownloader == downloader)
- {
- id<SDWebImageManagerDelegate> delegate = [delegates objectAtIndex:idx];
-
- if (image && [delegate respondsToSelector:@selector(webImageManager:didFinishWithImage:)])
- {
- [delegate performSelector:@selector(webImageManager:didFinishWithImage:) withObject:self withObject:image];
- }
+ id<SDWebImageManagerDelegate> delegate = [delegates objectAtIndex:idx];
- [downloaders removeObjectAtIndex:idx];
- [delegates removeObjectAtIndex:idx];
+ if (image && [delegate respondsToSelector:@selector(webImageManager:didFinishWithImage:)])
+ {
+ [delegate performSelector:@selector(webImageManager:didFinishWithImage:) withObject:self withObject:image];
}
+
+ [downloaders removeObjectAtIndex:idx];
+ [delegates removeObjectAtIndex:idx];
}
}
3  src/Sample/Classes/SDWebImageSample/SDWebImage/UIImageView+WebCache.h
View
@@ -4,7 +4,7 @@
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
- */
+ */
#import <UIKit/UIKit.h>
#import "SDWebImageManagerDelegate.h"
@@ -13,5 +13,6 @@
- (void)setImageWithURL:(NSURL *)url;
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
+- (void)cancelCurrentImageLoad;
@end
20 src/Sample/Classes/SDWebImageSample/SDWebImage/UIImageView+WebCache.m
View
@@ -4,7 +4,7 @@
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
- */
+ */
#import "UIImageView+WebCache.h"
#import "SDWebImageManager.h"
@@ -23,7 +23,11 @@ - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder
// Remove in progress downloader from queue
[manager cancelForDelegate:self];
- UIImage *cachedImage = [manager imageWithURL:url];
+ UIImage *cachedImage = nil;
+ if (url)
+ {
+ cachedImage = [manager imageWithURL:url];
+ }
if (cachedImage)
{
@@ -36,13 +40,21 @@ - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder
self.image = placeholder;
}
- [manager downloadWithURL:url delegate:self];
+ if (url)
+ {
+ [manager downloadWithURL:url delegate:self];
+ }
}
}
+- (void)cancelCurrentImageLoad
+{
+ [[SDWebImageManager sharedManager] cancelForDelegate:self];
+}
+
- (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)image
{
self.image = image;
}
-@end
+@end
Please sign in to comment.
Something went wrong with that request. Please try again.