1

I am using the AWS SDK for iOS to upload and download files to and from local hard drive to Amazon S3 storage. I am capable of making this work but I am unable to get the S3 delegate to respond properly to alert me when operations have finished or resulted in an error.

I have an array of files that I want to upload. For each file I create a NSOperation where the main routine consist mostly of:

    AmazonCredentials * credentials = [[AmazonCredentials alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY];
    putObjectRequest = [[S3PutObjectRequest alloc] initWithKey:pathCopy inBucket:[self bucket]];
    putObjectRequest.filename = pathSource;
    putObjectRequest.credentials=credentials;
    [putObjectRequest setDelegate:s3Delegate];

Here, the delegate (s3Delegate) is created as a regular AmazonServiceRequestDelegate which should be able to fire off responses when an operation has finished. Each of my NSOperations are added to my NSOperationQueue which executes operations non-concurrently. If I use the delegate [putObjectRequest setDelegate:s3Delegate] the operations are not working. If I remove the use of the delegate the operations are performed correctly but I am unable to receive any responses to the operations as I do not have a delegate.

If I remove the use of the NSOperationQueue completely and use the [putObjectRequest setDelegate:s3Delegate] the delegate works perfectly.

My question is what am I doing wrong with using a delegate in a queue? Since the delegate is perfectly capable of performing while not in a queue could this be related to not performing on the main thread? I really want to be able to use the queue to limit the number of non-concurrent operations, however I am unable to figure this out. I hope someone has an idea of what is going on here and any example code would be greatly appreciated. Thanks! Cheers, Trond

Rob Keniger
  • 45,830
  • 6
  • 101
  • 134
Trond Kristiansen
  • 2,379
  • 23
  • 48

1 Answers1

8

It seems that the aws sdk behaves asynchronously after the time you set your delegate. So in order to have your asynchronous aws stuff work in a (asynchronous) NSOperation, you got to put some magic to wait for AWS to complete:

In your .h NSOperation file, add a boolean:

@interface UploadOperation : NSOperation <AmazonServiceRequestDelegate> {
    @private
    BOOL        _doneUploadingToS3;
}

and in your .m file, your main method will look like this:

- (void) main
{   
    ....  do your stuff …..

    _doneUploadingToS3 = NO;

    S3PutObjectRequest *por = nil;
    AmazonS3Client *s3Client = [[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY withSecretKey:SECRET_KEY];
    s3Client.endpoint = endpoint;

    @try {
        por = [[[S3PutObjectRequest alloc] initWithKey:KEY inBucket:BUCKET] autorelease];
        por.delegate = self;
        por.contentType = @"image/jpeg";
        por.data = _imageData;

        [s3Client putObject:por];
    }
    @catch (AmazonClientException *exception) {
        _doneUploadingToS3 = YES;
    }

    do {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    } while (!_doneUploadingToS3);

    por.delegate = nil;

    ....  continue with your stuff ….
}

do not forget to implement your delegate methods

-(void)request:(AmazonServiceRequest *)request didCompleteWithResponse:(AmazonServiceResponse *)response
{
    _doneUploadingToS3 = YES;
}

-(void)request:(AmazonServiceRequest *)request didFailWithError:(NSError *)error 
{
    _doneUploadingToS3 = YES;
}

-(void)request:(AmazonServiceRequest *)request didFailWithServiceException:(NSException *)exception 
{
    _doneUploadingToS3 = YES;
}

- (void) request:(AmazonServiceRequest *)request didSendData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
    // Do what you want
}

-(void)request:(AmazonServiceRequest *)request didReceiveResponse:(NSURLResponse *)response
{
    // Do what you want
}

-(void)request:(AmazonServiceRequest *)request didReceiveData:(NSData *)data
{
    // Do what you want
}

Note: this magic can work for any stuff that performs asynchronously but have to be implemented in a NSOperation.

romrom
  • 642
  • 1
  • 6
  • 14
  • Thank you! It worked perfectly with your suggestions. It seems the trick to make this work was the code: `do {[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];} while (!_doneUploadingToS3);` i tried to run the operation without this snippet and it did not work. When I include it, the delegate works seamlessly. Could you explain to me why this code is so important and what it does? Thanks again!. Cheers, Trond – Trond Kristiansen Nov 26 '11 at 21:34
  • 1
    This loop waits for _doneUploadingToS3 to be set to YES. Until then it loops (without overloading your CPU though). When one of the completion method of the AWS SDK is fired, _doneUploadingToS3 is set to YES, and the rest of the main method is executed. – romrom Nov 28 '11 at 16:02
  • Thank you so much! This was exactly my problem. Tags: AmazonServiceRequestDelegate not working Nsoperation nsoperationqueue – Jords Dec 03 '11 at 01:05
  • Just a quick note if you're using an AmazonServiceRequestDelegate with a multi part upload, you need to also move the code where you would normally add the part number to the completion request (e.g. [completionRequest addPartWithPartNumber:(partRequest.partNumber) withETag:mpResponse.etag];) to the didCompleteWithResponse method in the delegate as you will get a nil return from the uploadPart method on the s3 client. – codeghost Sep 06 '12 at 09:00