Sending emails in sequential order using GCD

When a user clicks on a picture, the image should be sent to their mail. I want this to happen in the background, so I am using SKPSMTPMessage. I get an error if the user clicks on the picture before the previous post / Uploaded. I thought I would be using GCD for serialization, but I can't seem to work it out.

- (IBAction)snapStillImage:(id)sender
{

        //code after taking the picture
            if([uploadMethod isEqualToString:@"Mail"]){
                time_t unixTime = (time_t) [[NSDate date] timeIntervalSince1970];
                NSString *timestamp=[NSString stringWithFormat:@"%ld",unixTime];
                NSData *jpgData = UIImageJPEGRepresentation(image, 0.5);
                [jpgData writeToFile:[self documentsPathForFileName:timestamp] atomically:YES];
                [self.imagesArray addObject:timestamp];
                [self sendMail];

}


-(void)sendMail{
dispatch_queue_t myFifoQueue = dispatch_queue_create("com.xxxx.uploadpictures",DISPATCH_QUEUE_SERIAL);
if(!self.imagesArray.count == 0){
    for(NSString *str in self.imagesArray){
        dispatch_async(myFifoQueue,^{
            [self sendMailWith:str];
        });
    }
}
}

      

+3


source to share


2 answers


A few observations:

  • As Andrew points out, you only want to create one queue and not create a new queue every time.

  • Given that it SKPSMTPMessage

    runs asynchronously, if you want them to run sequentially, you need to refactor this code so that the next request does not run until the asynchronous request is complete. A typical solution for this is:

    • Use operation queues ( NSOperationQueue

      ).

    • Wrap your asynchronous send request in a parallel subclass NSOperation

      . You would set yours NSOperation

      as delegate

      for the object SKPSMTPMessage

      , and in the methods messageSent

      and messageFailed

      you would perform the operation (place the necessary isFinished

      and isExecuting

      notifications).

      For more information, see the Configuring Operations for Parallel Execution section of the Operation Queues chapter in the Concurrency Programming Guide.

    • Make sure you configure your queue or operations to be sent sequentially (for example, set the activity queue maxConcurrentOperationCount

      to 1 to make it sequential).


To illustrate, you should define a queue of operations:

@property (nonatomic, strong) NSOperationQueue *mailQueue;

      

You can set it up once:

self.mailQueue = [[NSOperationQueue alloc] init];
self.mailQueue.maxConcurrentOperationCount = 1;

      

And then you add post requests to this queue:

[self.mailQueue addOperation:[[SendEmailOperation alloc] initWithTo:@"rob@example.com"
                                                            subject:@"title"
                                                               body:@"body"
                                                               path:pathToImage]];

      



So the interesting question is what this looks like SendMailOperation

:

//  SendEmailOperation.h

#import "ConcurrentOperation.h"

@interface SendEmailOperation : ConcurrentOperation

@property (nonatomic, copy) NSString *to;
@property (nonatomic, copy) NSString *subject;
@property (nonatomic, copy) NSString *body;
@property (nonatomic, copy) NSString *path;

- (instancetype)initWithTo:(NSString *)to subject:(NSString *)subject body:(NSString *)body path:(NSString *)path;

@end

      

and

//  SendEmailOperation.m

#import "SendEmailOperation.h"
#import "SKPSMTPMessage.h"

@interface SendEmailOperation () <SKPSMTPMessageDelegate>

@end

@implementation SendEmailOperation

- (instancetype)initWithTo:(NSString *)to subject:(NSString *)subject body:(NSString *)body path:(NSString *)path
{
    self = [super init];
    if (self) {
        self.to = to;
        self.subject = subject;
        self.body = body;
        self.path = path;
    }
    return self;
}

- (void)main
{
    SKPSMTPMessage *message = [self createMessage]; // configure your message like you are now
    message.delegate = self;
    [message send];
}

- (SKPSMTPMessage *)createMessage
{
    SKPSMTPMessage *message = [[SKPSMTPMessage alloc] init];

    // configure this message like you are now, picking up the various class properties
    // (e.g. to, subject, body, etc.).

    return message;
}

#pragma mark - SKPSMTPMessageDelegate

-(void)messageSent:(SKPSMTPMessage *)message
{
    [self completeOperation];
}

-(void)messageFailed:(SKPSMTPMessage *)message error:(NSError *)error
{
    NSLog(@"%s: %@", __PRETTY_FUNCTION__, error);

    [self completeOperation];
}

@end

      

And finally, the guts of the parallel operation subclass are waterless in that class ConcurrentOperation

. Once you start using parallel operations, you will find that you use this pattern very often, so this class saves you the trouble of constantly re-arranging the subclass material of your current work:

//  ConcurrentOperation.h

#import <Foundation/Foundation.h>

@interface ConcurrentOperation : NSOperation

- (void)completeOperation;

@end

      

and

//  ConcurrentOperation.m

#import "ConcurrentOperation.h"

@interface ConcurrentOperation ()

@property (nonatomic, getter = isFinished, readwrite)  BOOL finished;
@property (nonatomic, getter = isExecuting, readwrite) BOOL executing;

@end

@implementation ConcurrentOperation

@synthesize finished  = _finished;
@synthesize executing = _executing;

- (id)init
{
    self = [super init];
    if (self) {
        _finished  = NO;
        _executing = NO;
    }
    return self;
}

- (void)start
{
    if ([self isCancelled]) {
        self.finished = YES;
        return;
    }

    self.executing = YES;

    [self main];
}

- (void)completeOperation
{
    self.executing = NO;
    self.finished  = YES;
}

#pragma mark - NSOperation methods

- (BOOL)isConcurrent
{
    return YES;
}

- (void)setExecuting:(BOOL)executing
{
    if (_executing != executing) {
        [self willChangeValueForKey:@"isExecuting"];
        _executing = executing;
        [self didChangeValueForKey:@"isExecuting"];
    }
}

- (void)setFinished:(BOOL)finished
{
    if (_finished != finished) {
        [self willChangeValueForKey:@"isFinished"];
        _finished = finished;
        [self didChangeValueForKey:@"isFinished"];
    }
}

@end

      

I know this looks like a lot, but once you have ConcurrentOperation

(or something like that) in your tool belt, you will find that creating parallel operations is very easy and incredibly useful when you want to execute a sequence of asynchronous tasks sequentially (or at the same time with some special dependencies).

+5


source


The code, as written now, creates a new queue every time the method is called sendMail

and sends the block to this new queue, so no serial execution takes place.

To fix this, you can make myFifoQueue

an instance variable (for example, create it in a method init

) or make it static, for example:



-(void)sendMail {
    dispatch_queue_t myFifoQueue;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        myFifoQueue = dispatch_queue_create(...);
    }

    if(!self.imagesArray.count == 0) {
        ...
    }
}

      

+3


source







All Articles