Using SDWebImage with Images on Amazon S3: A Brief Tutorial from MAZ
For those who don’t know how to read and write code, the “backend” of an app often goes unnoticed when using your phone or computer. But everything that you, as an end user, consider to work well comes from what you don’t see: the code.
For those who do know about code, the below script will make a lot of sense! Let's begin with two terms I'd like to discuss, relating to how an iPhone app user views images that are sourced from the web.
SDWebImage: A handy iOS library, which scours the internet to download, cache and display web-sourced images.
Amazon S3 Server: An encrypted cloud storage service provided by Amazon for storing images.
You may use or have heard about MAZ’s new mobile browser Stream Web (available free for iOS here), for which I am a developer. The whole concept behind Stream Web requires images to be stored and recalled instantaneously. I was introduced to SDWebImage when we began work on Stream, the flagship feature behind Stream Web and our app publishing platform.
I noticed that using S3 with SDWebImage could be pretty powerful for developers, but also realized that some developers might not be able to figure out how to use them together since SDWebImage does not support S3 validation (at least not yet!). Delving into the SDWebImage source to code around this issue would not be too helpful, because if a new version of SDWebImage were to be released, the developer would have to redo everything. Instead, a better approach (provided here) would be to keep SDWebImage untouched, and to intercept all its download requests and handle the S3 server downloads via the Amazon S3 SDK.
If you’d like to see what I mean, try the following:
1) Set up SDWebImage and Amazon's S3 SDKs (with credentials)
// File: AmazonClientManager.m
+ (void)validateCredentials
{
// ...
s3 = [[AmazonS3Client alloc] initWithAccessKey:<YOUR ACCESS KEY> withSecretKey:<YOUR SECRET KEY>];
// ...
}
2) Subclass NSURL Protocol
// File: S3URLProtocol.h
@interface S3URLProtocol : NSURLProtocol
@end
// File: S3URLProtocol.m
#import "S3URLProtocol.h"
#import "AmazonClientManager.h"
#define kS3BucketName <YOUR BUCKET NAME>
@interface S3URLProtocol() <AmazonServiceRequestDelegate>
@property (strong, nonatomic) S3GetObjectRequest *getRequest;
@end
@implementation S3URLProtocol
// All registered protocols are asked if they can handle the request
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
// If the host is not Amazon S3 Server return 'NO' to indicate default handling
if(![request.URL.host isEqualToString:[kS3BucketName stringByAppendingString:@".s3.amazonaws.com"]])
return NO;
// Yes we can handle it! Go 'startLoading'!
return YES;
}
// Required method
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
return request;
}
- (void)startLoading
{
// Extract the key from the request
NSString *key = self.request.URL.path;
key = [key substringFromIndex:1];
// Create a 'S3GetObjectRequest' with your bucket and the extracted key
self.getRequest = [[S3GetObjectRequest alloc] initWithKey:key withBucket:kS3BucketName];
// Set delegate to ourselves to handle various callbacks.
self.getRequest.delegate = self;
// And fire the download. The 'AmazonClientManager' will handle the 'Authentication' for us.
[[AmazonClientManager s3] getObject:self.getRequest];
}
- (void)stopLoading
{
// If someone cancels this, cancel the 'S3GetObjectRequest' in turn.
[self.getRequest cancel];
self.getRequest = nil;
}
// The 'AmazonServiceRequestDelegate' callbacks
- (void)request:(AmazonServiceRequest *)request didReceiveResponse:(NSURLResponse *)response
{
// We received a response, pass it on to the client (SDWebImage).
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
}
- (void)request:(AmazonServiceRequest*)request didReceiveData:(NSData*)data
{
// We received some data, pass it on to the client (SDWebImage).
[self.client URLProtocol:self didLoadData:data];
}
- (void)request:(AmazonServiceRequest *)request didCompleteWithResponse:(AmazonServiceResponse *)response
{
// Download complete! Notify client (SDWebImage) and cleanup.
[self.client URLProtocolDidFinishLoading:self];
self.getRequest = nil;
}
- (void)request:(AmazonServiceRequest *)request didFailWithError:(NSError *)error
{
// Some error encountered! Notify client (SDWebImage) and cleanup.
[self.client URLProtocol:self didFailWithError:error];
self.getRequest = nil;
}
@end
3) Finally, hook up our shiny new "S3URLProtocol" by adding (just once!) this line to your initialization code:
[NSURLProtocol registerClass:[S3URLProtocol class]];
Now simply use the SDWebImage's categories e.g. [imageView setImageWithURL:
<IMAGE FILE URL> ...]; the host of <IMAGE FILE URL> should be equal to "<YOUR BUCKET NAME>.s3.amazonaws.com ".
Now you can use the power of the Amazon S3 cloud servers with SDWebImage in your own apps! How neat is that?
Hemant Dabral is an iOS and Android Developer in MAZ's Noida, India office. Check him out on Twitter!











