2009-09-21 22:03:45 +08:00
|
|
|
Web Image
|
|
|
|
=========
|
2009-09-20 02:45:42 +08:00
|
|
|
|
2009-09-23 09:05:40 +08:00
|
|
|
This library provides a category for UIImageVIew with support for remote images coming from the web.
|
2009-09-20 02:45:42 +08:00
|
|
|
|
2009-09-20 03:14:40 +08:00
|
|
|
It provides:
|
2009-09-21 10:29:00 +08:00
|
|
|
|
2009-09-24 05:22:48 +08:00
|
|
|
- An UIImageView category adding web image and cache management to the Cocoa Touch framework
|
2010-06-09 10:09:18 +08:00
|
|
|
- An asynchronous image downloader
|
2009-09-24 05:22:48 +08:00
|
|
|
- An asynchronous memory + disk image caching with automatic cache expiration handling
|
2011-10-20 21:03:19 +08:00
|
|
|
- A guarantee that the same URL won't be downloaded several times
|
|
|
|
- A guarantee that bogus URLs won't be retried again and again
|
2009-09-24 05:22:48 +08:00
|
|
|
- Performances!
|
2009-09-20 03:14:40 +08:00
|
|
|
|
2009-09-21 11:34:04 +08:00
|
|
|
Motivation
|
|
|
|
----------
|
|
|
|
|
2009-09-21 22:03:45 +08:00
|
|
|
As a dummy Objective-C developer working on my first iPhone application for my company
|
|
|
|
([Dailymotion][]), I've been very frustrated by the lack of support in the Cocoa Touch framework for
|
2011-10-20 21:03:19 +08:00
|
|
|
UITableView with remote images. After some Googling, I found lot of forums and blogs coming up with
|
|
|
|
their solution, most of the time based on asynchronous usage with NSURLConnection, but none provided
|
2009-09-21 22:03:45 +08:00
|
|
|
a simple library doing the work of async image grabbing + caching for you.
|
|
|
|
|
2011-10-20 21:03:19 +08:00
|
|
|
Actually there is one in the famous [Three20][] framework by [Joe Hewitt][], but it's a massive
|
2009-09-21 22:03:45 +08:00
|
|
|
and undocumented piece of code. You can't import just the the libraries you want without taking the
|
|
|
|
whole framework (damn #import "TTGlobal.h"). Anyway, the [Three20][] implementation is based on
|
2011-10-20 21:03:19 +08:00
|
|
|
NSURLConnection, and I soon discovered this solution wasn't ideal. Keep reading to find out why.
|
2009-09-21 22:03:45 +08:00
|
|
|
|
2011-10-20 21:03:19 +08:00
|
|
|
As a hurried beginner in iPhone development, I couldn't attempt to implement my own async image
|
|
|
|
grabber with caching support as my first steps in this new world. Thus, I asked for help from my good
|
2009-09-21 22:03:45 +08:00
|
|
|
friend Sebastien Flory ([Fraggle][]), who was working on his great iPhone game ([Urban Rivals][], a
|
2011-10-20 21:03:19 +08:00
|
|
|
future app-store hit) for almost a year. He spent quite an amount of time implementing the very
|
2009-09-21 22:03:45 +08:00
|
|
|
same solution for his needs, and was kind enough to give me his implementation for my own use. This
|
|
|
|
worked quite well and allowed me to concentrate on other parts of my application. But when I started
|
|
|
|
to compare my application with its direct competitor - the built-in Youtube application - I was very
|
|
|
|
unhappy with the loading speed of the images. After some network sniffing, I found that every HTTP
|
2011-10-20 21:03:19 +08:00
|
|
|
requests for my images was 10 times slower than Youtube's... On my own network, Youtube was 10
|
2009-09-21 22:03:45 +08:00
|
|
|
time faster than my own servers... WTF??
|
|
|
|
|
2011-10-20 21:03:19 +08:00
|
|
|
In fact, my servers were fine 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. Right then, I
|
2010-06-09 10:09:18 +08:00
|
|
|
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.
|
|
|
|
|
2011-10-20 21:03:19 +08:00
|
|
|
A simple test to recognize an application using NSURLConnection in its default mode to load
|
2010-06-09 10:09:18 +08:00
|
|
|
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.
|
|
|
|
|
2011-10-20 21:03:19 +08:00
|
|
|
This technique combined with an image cache instantly gave a lot of responsiveness to my app.
|
|
|
|
I thought this library could benefit other Cocoa Touch applications so I open-sourced it.
|
2009-09-21 11:34:04 +08:00
|
|
|
|
2009-09-20 03:14:40 +08:00
|
|
|
How To Use It
|
|
|
|
-------------
|
|
|
|
|
2009-09-23 09:05:40 +08:00
|
|
|
### Using UIImageView+WebCache category with UITableView
|
2009-09-21 10:29:00 +08:00
|
|
|
|
2009-09-24 05:22:48 +08:00
|
|
|
Just #import the UIImageView+WebCache.h header, and call the setImageWithURL:placeholderImage:
|
|
|
|
method from the tableView:cellForRowAtIndexPath: UITableViewDataSource method. Everything will be
|
2010-06-09 10:09:18 +08:00
|
|
|
handled for you, from async downloads to caching management.
|
2009-09-21 10:29:00 +08:00
|
|
|
|
2009-09-23 09:05:40 +08:00
|
|
|
#import "UIImageView+WebCache.h"
|
2009-09-21 10:29:00 +08:00
|
|
|
|
2009-09-23 09:05:40 +08:00
|
|
|
...
|
|
|
|
|
|
|
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
|
|
{
|
|
|
|
static NSString *MyIdentifier = @"MyIdentifier";
|
|
|
|
|
|
|
|
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
|
|
|
|
|
|
|
|
if (cell == nil)
|
|
|
|
{
|
|
|
|
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
|
|
|
|
reuseIdentifier:MyIdentifier] autorelease];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Here we use the new provided setImageWithURL: method to load the web image
|
2009-09-24 05:22:48 +08:00
|
|
|
[cell.imageView setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
|
|
|
|
placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
|
2009-09-23 09:05:40 +08:00
|
|
|
|
|
|
|
cell.textLabel.text = @"My Text";
|
|
|
|
return cell;
|
|
|
|
}
|
2009-09-21 10:29:00 +08:00
|
|
|
|
2009-09-24 05:22:48 +08:00
|
|
|
### Using SDWebImageManager
|
|
|
|
|
|
|
|
The SDWebImageManager is the class behind the UIImageView+WebCache category. It ties the
|
2011-10-20 21:03:19 +08:00
|
|
|
asynchronous downloader with the image cache store. You can use this class directly to benefit
|
|
|
|
from web image downloading with caching in another context than a UIView (ie: with Cocoa).
|
2009-09-24 05:22:48 +08:00
|
|
|
|
|
|
|
Here is a simple example of how to use SDWebImageManager:
|
|
|
|
|
|
|
|
SDWebImageManager *manager = [SDWebImageManager sharedManager];
|
|
|
|
|
|
|
|
UIImage *cachedImage = [manager imageWithURL:url];
|
|
|
|
|
|
|
|
if (cachedImage)
|
|
|
|
{
|
|
|
|
// Use the cached image immediatly
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Start an async download
|
|
|
|
[manager downloadWithURL:url delegate:self];
|
|
|
|
}
|
|
|
|
|
|
|
|
Your class will have to implement the SDWebImageManagerDelegate protocol, and to implement the
|
2010-06-11 20:40:44 +08:00
|
|
|
webImageManager:didFinishWithImage: method from this protocol:
|
2009-09-24 05:22:48 +08:00
|
|
|
|
2010-06-11 20:40:44 +08:00
|
|
|
- (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)image
|
2009-09-24 05:22:48 +08:00
|
|
|
{
|
|
|
|
// Do something with the downloaded image
|
|
|
|
}
|
|
|
|
|
|
|
|
### Using Asynchronous Image Downloader Independently
|
2009-09-21 10:29:00 +08:00
|
|
|
|
2010-06-09 10:09:18 +08:00
|
|
|
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:.
|
2009-09-21 10:29:00 +08:00
|
|
|
|
2009-09-24 05:22:48 +08:00
|
|
|
downloader = [SDWebImageDownloader downloaderWithURL:url delegate:self];
|
2009-09-21 10:29:00 +08:00
|
|
|
|
2010-06-09 10:09:18 +08:00
|
|
|
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.
|
2009-09-21 10:29:00 +08:00
|
|
|
|
2009-09-24 05:22:48 +08:00
|
|
|
### Using Asynchronous Image Caching Independently
|
2009-09-21 10:29:00 +08:00
|
|
|
|
2009-09-22 01:34:32 +08:00
|
|
|
It is also possible to use the NSOperation based image cache store independently. SDImageCache
|
2009-09-21 22:03:45 +08:00
|
|
|
maintains a memory cache and an optional disk cache. Disk cache write operations are performed
|
|
|
|
asynchronous so it doesn't add unnecessary latency to the UI.
|
2009-09-21 10:29:00 +08:00
|
|
|
|
2009-09-22 01:34:32 +08:00
|
|
|
The SDImageCache class provides a singleton instance for convenience but you can create your own
|
2011-10-20 21:03:19 +08:00
|
|
|
instance if you want to create separated cache namespace.
|
2009-09-21 10:29:00 +08:00
|
|
|
|
2009-09-21 22:03:45 +08:00
|
|
|
To lookup the cache, you use the imageForKey: method. If the method returns nil, it means the cache
|
2011-10-20 21:03:19 +08:00
|
|
|
doesn't currently own the image. You are thus responsible for generating and caching it. The cache
|
2009-09-21 22:03:45 +08:00
|
|
|
key is an application unique identifier for the image to cache. It is generally the absolute URL of
|
|
|
|
the image.
|
2009-09-21 10:29:00 +08:00
|
|
|
|
2009-09-22 01:34:32 +08:00
|
|
|
UIImage *myCachedImage = [[SDImageCache sharedImageCache] imageFromKey:myCacheKey];
|
2009-09-21 10:29:00 +08:00
|
|
|
|
2009-09-22 01:34:32 +08:00
|
|
|
By default SDImageCache will lookup the disk cache if an image can't be found in the memory cache.
|
2009-09-21 22:03:45 +08:00
|
|
|
You can prevent this from happening by calling the alternative method imageFromKey:fromDisk: with a
|
|
|
|
negative second argument.
|
2009-09-21 10:29:00 +08:00
|
|
|
|
|
|
|
To store an image into the cache, you use the storeImage:forKey: method:
|
|
|
|
|
2009-09-22 01:34:32 +08:00
|
|
|
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];
|
2009-09-20 03:14:40 +08:00
|
|
|
|
2009-09-21 22:03:45 +08:00
|
|
|
By default, the image will be stored in memory cache as well as on disk cache (asynchronously). If
|
|
|
|
you want only the memory cache, use the alternative method storeImage:forKey:toDisk: with a negative
|
|
|
|
third argument.
|
2009-09-20 03:14:40 +08:00
|
|
|
|
2012-03-11 00:40:02 +08:00
|
|
|
Automatic Reference Counting (ARC)
|
|
|
|
----------------------------------
|
|
|
|
|
|
|
|
You can use either style in your Cocoa project. SDWebImage Will figure out which you are using at compile
|
|
|
|
time and do the right thing.
|
|
|
|
|
|
|
|
|
2012-01-28 06:41:43 +08:00
|
|
|
Installation
|
|
|
|
------------
|
|
|
|
|
2012-03-11 00:40:02 +08:00
|
|
|
You can chose to copy all the files in your project or to import the it as a static library.
|
2012-01-28 06:41:43 +08:00
|
|
|
|
2012-03-11 00:40:02 +08:00
|
|
|
The following instructions are adapted from the excellent "Using Open Source Static Libraries in Xcode 4"
|
2012-01-28 06:41:43 +08:00
|
|
|
[tutorial][] from Jonah Williams.
|
|
|
|
|
|
|
|
### Add the SDWebImage project to your workspace
|
|
|
|
|
|
|
|
Right-click on the project navigator and select "Add Files to "Your Project" and select the SDWebImage
|
|
|
|
Xcode project.
|
|
|
|
|
|
|
|
![Add SDWebImage](http://blog.carbonfive.com/wp-content/uploads/2011/04/adding_an_existing_project.png?w=300)
|
|
|
|
|
|
|
|
You should end up with your project and SDWebimage project at the same lever in the workspace.
|
|
|
|
|
2012-02-22 23:37:23 +08:00
|
|
|
### Add build target dependency
|
2012-01-28 06:41:43 +08:00
|
|
|
|
2012-03-11 00:40:02 +08:00
|
|
|
Select your project's build target and add the 'libSDWebImage.a' library to the "Link Binary With Libraries"
|
2012-01-28 06:41:43 +08:00
|
|
|
build phase.
|
|
|
|
|
2012-02-22 23:37:23 +08:00
|
|
|
![Add target dependency](http://blog.carbonfive.com/wp-content/uploads/2011/04/linkable_libraries.png?w=214)
|
2012-01-28 06:41:43 +08:00
|
|
|
|
|
|
|
### Add headers
|
|
|
|
|
2012-03-11 00:40:02 +08:00
|
|
|
Open the "Build Settingsæ tab and locate the "User Header Search Paths" setting. Set this to
|
|
|
|
"$(BUILT_PRODUCTS_DIR)" and check the "Recursive" check box.
|
2012-01-28 06:41:43 +08:00
|
|
|
|
|
|
|
![Header Search Paths](http://blog.carbonfive.com/wp-content/uploads/2011/04/header_search_path_value.png?w=300)
|
|
|
|
|
2012-03-11 00:40:02 +08:00
|
|
|
Add the "-ObjC" flag to the "Other Linker Flags" build setting.
|
2012-01-28 06:41:43 +08:00
|
|
|
|
|
|
|
### Fixing indexing
|
|
|
|
|
|
|
|
If you have problem with auto-completion of SDWebImage methods, you may have to copy the header files in
|
|
|
|
your project.
|
|
|
|
|
|
|
|
|
|
|
|
|
2009-09-20 03:14:40 +08:00
|
|
|
Future Enhancements
|
|
|
|
-------------------
|
|
|
|
|
2009-09-21 09:43:43 +08:00
|
|
|
- LRU memory cache cleanup instead of reset on memory warning
|
2009-09-21 22:03:45 +08:00
|
|
|
|
|
|
|
[Dailymotion]: http://www.dailymotion.com
|
|
|
|
[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
|
2010-06-09 10:09:18 +08:00
|
|
|
[Joe Hewitt]: http://www.joehewitt.com
|
2012-01-28 06:41:43 +08:00
|
|
|
[tutorial]: http://blog.carbonfive.com/2011/04/04/using-open-source-static-libraries-in-xcode-4
|