AWS S3 Image upload using AWS SDK for iOS v2

Hi there and welcome. Today we will be talking about AWS S3 Image upload using AWS SDK for iOS v2 (WOW thats a lot of acronyms!). Last week I had to do this in a project and it took me and I was disappointed with the documentation, and sample project they had available. So in this tutorial I’m going to be showing a real world example where you would select an image from the gallery or take a photo on a device and upload that image to your bucket. If you want to follow along I have the projects available on github for both objective-c and swift! I also have a video of setting up S3 and Amazon Cognito at the end of the tutorial, but I have to apologize for the quality. I am terrible at making videos, and I was very tired when I made it (also my voice is very annoying).

Github Links

Also don’t forget to follow me on twitter if you ever want to chat about iOS or mobile development! .

Set up credentials

First thing we need to do is set up amazons credentials your app delegate’s didFinishLaunchingWithOptions method. In this demo we are going to use Cognito to manage access to our S3 bucket. If you want a full in depth look at how to do this watch the video at the end of the tutorial.

Objective-C

AppDelegate.m

//
//  AppDelegate.m
//  s3-objectiveC
//
//  Created by Barrett Breshears on 12/5/14.
//  Copyright (c) 2014 Barrett Breshears. All rights reserved.
//

#import "AppDelegate.h"
#import "AWSCore.h"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    AWSCognitoCredentialsProvider *credentialsProvider = [AWSCognitoCredentialsProvider
                                                          credentialsWithRegionType:AWSRegionUSEast1
                                                          accountId:@"#######"
                                                          identityPoolId:@"######"
                                                          unauthRoleArn:@"#####"
                                                          authRoleArn:@"######"];
    
    AWSServiceConfiguration *configuration = [AWSServiceConfiguration configurationWithRegion:AWSRegionUSEast1
                                                                          credentialsProvider:credentialsProvider];
    
    [AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration;
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
}

- (void)applicationWillTerminate:(UIApplication *)application {
}

@end

Swift

AppDelegate.swift

//
//  AppDelegate.swift
//  s3-swift
//
//  Created by Barrett Breshears on 12/6/14.
//  Copyright (c) 2014 Barrett Breshears. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        var credentialsProvider:AWSCognitoCredentialsProvider = AWSCognitoCredentialsProvider.credentialsWithRegionType(AWSRegionType.USEast1, accountId:"##########", identityPoolId:"#######", unauthRoleArn:"#######", authRoleArn:"########")
        
        var configuration:AWSServiceConfiguration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: credentialsProvider)
        
        AWSServiceManager.defaultServiceManager().setDefaultServiceConfiguration(configuration)
        
        
        
        return true
    }

    func applicationWillResignActive(application: UIApplication) {
    }

    func applicationDidEnterBackground(application: UIApplication) {
    }

    func applicationWillEnterForeground(application: UIApplication) {
    }

    func applicationDidBecomeActive(application: UIApplication) {
    }

    func applicationWillTerminate(application: UIApplication) {
    }


}

This is a pretty simple set up here, and if you configure your credentials in the Cognito console it will actually give you the code to set up your configuration.

Upload an image to S3

So the main difference between v1 and v2 is that you used to be able to set the upload body request to NSData or a NSURL. In v2 you can only set the upload request to a NSURL. So what we have to do is save the image to the app’s local directory and then we can create a local file url to send to amazon.

Objective-C
- (void)uploadToS3{
    // get the image from a UIImageView that is displaying the selected Image
    UIImage *img = _selectedImage.image;
    
    // create a local image that we can use to upload to s3
    NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"image.png"];
    NSData *imageData = UIImagePNGRepresentation(img);
    [imageData writeToFile:path atomically:YES];
    
    // once the image is saved we can use the path to create a local fileurl
    NSURL *url = [[NSURL alloc] initFileURLWithPath:path];
    
    // next we set up the S3 upload request manager
    _uploadRequest = [AWSS3TransferManagerUploadRequest new];
    // set the bucket
    _uploadRequest.bucket = @"s3-demo-objectivec";
    // I want this image to be public to anyone to view it so I'm setting it to Public Read
    _uploadRequest.ACL = AWSS3ObjectCannedACLPublicRead;
    // set the image's name that will be used on the s3 server. I am also creating a folder to place the image in
    _uploadRequest.key = @"foldername/image.png";
    // set the content type
    _uploadRequest.contentType = @"image/png";
    // we will track progress through an AWSNetworkingUploadProgressBlock
    _uploadRequest.body = url;
    
    __weak ViewController *weakSelf = self;
    
    _uploadRequest.uploadProgress =^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend){
        dispatch_sync(dispatch_get_main_queue(), ^{
            weakSelf.amountUploaded = totalBytesSent;
            weakSelf.filesize = totalBytesExpectedToSend;
            [weakSelf update];
            
        });
    };
    
    // now the upload request is set up we can creat the transfermanger, the credentials are already set up in the app delegate
    AWSS3TransferManager *transferManager = [AWSS3TransferManager defaultS3TransferManager];
    // start the upload
    [[transferManager upload:_uploadRequest] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id(BFTask *task) {
        
        // once the uploadmanager finishes check if there were any errors
        if (task.error) {
            NSLog(@"%@", task.error);
        }else{// if there aren't any then the image is uploaded!
            // this is the url of the image we just uploaded
            NSLog(@"https://s3.amazonaws.com/s3-demo-objectivec/foldername/image.png");
        }
        
        return nil;
    }];
    
}
Swift
 func uploadToS3(){
        
        // get the image from a UIImageView that is displaying the selected Image
        var img:UIImage = selectedImage!.image!
        
        // create a local image that we can use to upload to s3
        var path:NSString = NSTemporaryDirectory().stringByAppendingPathComponent("image.png")
        var imageData:NSData = UIImagePNGRepresentation(img)
        imageData.writeToFile(path, atomically: true)
        
        // once the image is saved we can use the path to create a local fileurl
        var url:NSURL = NSURL(fileURLWithPath: path)!
        
        // next we set up the S3 upload request manager
        uploadRequest = AWSS3TransferManagerUploadRequest()
        // set the bucket
        uploadRequest?.bucket = "s3-demo-swift"
        // I want this image to be public to anyone to view it so I'm setting it to Public Read
        uploadRequest?.ACL = AWSS3ObjectCannedACL.PublicRead
        // set the image's name that will be used on the s3 server. I am also creating a folder to place the image in
        uploadRequest?.key = "foldername/image.png"
        // set the content type
        uploadRequest?.contentType = "image/png"
        // and finally set the body to the local file path
        uploadRequest?.body = url;
        
        // we will track progress through an AWSNetworkingUploadProgressBlock
        uploadRequest?.uploadProgress = {[unowned self](bytesSent:Int64, totalBytesSent:Int64, totalBytesExpectedToSend:Int64) in
            
            dispatch_sync(dispatch_get_main_queue(), { () -> Void in
                self.amountUploaded = totalBytesSent
                self.filesize = totalBytesExpectedToSend;
                self.update()

            })
        }
        
        // now the upload request is set up we can creat the transfermanger, the credentials are already set up in the app delegate
        var transferManager:AWSS3TransferManager = AWSS3TransferManager.defaultS3TransferManager()
        // start the upload
        transferManager.upload(uploadRequest).continueWithExecutor(BFExecutor.mainThreadExecutor(), withBlock:{ [unowned self]
            task -> AnyObject in
            
            // once the uploadmanager finishes check if there were any errors
            if(task.error != nil){
                NSLog("%@", task.error);
            }else{ // if there aren't any then the image is uploaded!
                // this is the url of the image we just uploaded
                NSLog("https://s3.amazonaws.com/s3-demo-swift/foldername/image.png");
            }
            
            self.removeLoadingView()
            return "all done";
        })
        
    }

Once you understand how to configure your credentials and how to get the image from an image view to local file to url to amazon the process is pretty simple. Please let me know if you have any questions in the comments below or on send me a tweet
.

Setting up credentials and project walk through video

Singleton Objective C | iOS

There are times when developing an application I need to have a single instance of a class. For example recently I needed to build an audio player that managed a song queue, and if the queue was playing. I also needed to be able to modify the queue from another view controller, so it was the perfect time to utilize the singleton pattern.

Singleton Pattern

In my example I am creating a singleton using an NSObject, this basic principle can be used on an type object. So lets take a look at the header file:

//
//  MySingleton.h
//
//  Created by Barrett A Breshears 
//

#import 

@interface MySingleton : NSObject

@property (nonatomic, retain) NSString *singletonValue;

+(MySingleton *)sharedMySingleton;

@end

Its a simple header file with a single NSString property and class method declaration. Now lets take a look at the implementation file.

//
//  MySingleton.m
//
//  Created by Barrett A Breshears
//

#import "MySingleton.h"

@implementation MySingleton

static MySingleton *_sharedMySingleton = nil;

+(MySingleton*)sharedMySingleton{
    @synchronized([MySingleton class]) {
        if (!_sharedMySingleton){
            _sharedMySingleton = [[self alloc] init];
        }
        return _sharedMySingleton;
    }
    return nil;
}

+(id)alloc {
    @synchronized([MySingleton class])
    {
        NSAssert(_sharedMySingleton == nil, @"Singleton already initialized.");
        _sharedMySingleton = [super alloc];
        return _sharedMySingleton;
    }
    return nil;
}

-(id)init {
    self = [super init];
    if (self != nil) {
        // initialize stuff here
    }   return self;
}
@end

First I declare a static instance of the MySingleton Class. Next I write my class method which checks to see if there is an instance of the singleton already created. If there isn’t it goes ahead and alloc and inits itself:

+(MySingleton*)sharedMySingleton{
    @synchronized([MySingleton class]) {
        if (!_sharedMySingleton){
            _sharedMySingleton = [[self alloc] init];
        }
        return _sharedMySingleton;
    }
    return nil;
}

You will notice that I’m using @synchronized inside of all the methods related to referencing allocating and initializing the singleton. Apple’s docs on threading are great. Take some time to read them if you want to learn more about threading and sycronization, but here is a sinpit that is important to what we are working on:

The presence of multiple threads in an application opens up potential issues regarding safe access to resources from multiple threads of execution. Two threads modifying the same resource might interfere with each other in unintended ways. For example, one thread might overwrite another’s changes or put the application into an unknown and potentially invalid state. If you are lucky, the corrupted resource might cause obvious performance problems or crashes that are relatively easy to track down and fix. If you are unlucky, however, the corruption may cause subtle errors that do not manifest themselves until much later, or the errors might require a significant overhaul of your underlying coding assumptions.

When it comes to thread safety, a good design is the best protection you have. Avoiding shared resources and minimizing the interactions between your threads makes it less likely for those threads to interfere with each other. A completely interference-free design is not always possible, however. In cases where your threads must interact, you need to use synchronization tools to ensure that when they interact, they do so safely.

So the @synchronized directive is an easy way to allow us to lock the object so it can’t be corrupted by multiple threads.

Next I set up my alloc class method and init instance method, again using @synchronized directive to lock the object.

+(id)alloc {
    @synchronized([MySingleton class])
    {
        NSAssert(_sharedMySingleton == nil, @"Singleton already initialized.");
        _sharedMySingleton = [super alloc];
        return _sharedMySingleton;
    }
    return nil;
}

-(id)init {
    self = [super init];
    if (self != nil) {
        // initialize stuff here
    }   return self;
}

And that’s it. We can use the MySingleton class like this:

[MySingleton sharedMySingleton].singletonValue = @"Sledge Dev";

Using Singletons

The singleton is first pattern I learned when I start object oriented programming a long time ago. I really like it, and am probably guilty of overusing it myself. I have read a lot about how it not such a great idea to use a singleton, there are a ton of great articles out there if you want more info about this subject. Here are a couple of my favorites:

Personally I think there is a time and a place to use them, but please let me know what you think in the comments down below.