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];
});
}
}
}
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 yoursNSOperation
asdelegate
for the objectSKPSMTPMessage
, and in the methodsmessageSent
andmessageFailed
you would perform the operation (place the necessaryisFinished
andisExecuting
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).
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) {
...
}
}