1

My app is working this way : - create an album and take pictures - send them on my server - get an answer / complementary information after picture analysis.

I have some issue with the sending part. Here is the code

@interface SyncAgent : NSObject <SyncTaskDelegate>

@property  NSOperationQueue* _queue;
-(void)launchTasks;

@end


@implementation SyncAgent

@synthesize _queue;

- (id)init
{
    self = [super init];
    if (self) {
        self._queue = [[NSOperationQueue alloc] init];
        [self._queue setMaxConcurrentOperationCount:1];
    }
    return self;
}

-(void) launchTasks {

    NSMutableArray *tasks = [DataBase getPendingTasks];

    for(Task *t in tasks) {
        [self._queue addOperation:[[SyncTask alloc] initWithTask:t]];
    }
}
@end

and the SyncTask :

@interface SyncTask : NSOperation

@property (strong, atomic) Task *_task;
-(id)initWithTask:(Task *)task;
-(void)mainNewID;
-(void)mainUploadNextPhoto:(NSNumber*)photoSetID;

@end

@implementation SyncTask

@synthesize _task;

-(id)initWithTask:(Task *)task {
    if(self = [super init]) {
        self._task = task;
    }
    return self;
}

-(void)main {

    NSLog(@"Starting task : %@", [self._task description]);
    // checking if everything is ready, sending delegates a message etc

    [self mainNewID];
}

-(void)mainNewID {

    __block SyncTask *safeSelf = self;

    [[WebAPI sharedClient] createNewPhotoSet withErrorBlock:^{
        NSLog(@"PhotoSet creation : error")
    } andSuccessBlock:^(NSNumber *photoSetID) {
        NSLog(@"Photoset creation : id is %d", [photoSetID intValue]);
        [safeSelf mainUploadNextPhoto:photoSetID];
    }];
}

-(void)mainUploadNextPhoto:(NSNumber*) photoSetID {

    //just admit we have it. won't explain here how it's done
    NSString *photoPath;

    __block SyncTask *safeSelf = self;

    [[WebAPI sharedClient] uploadToPhotosetID:photoSetID withPhotoPath:photoPath andErrorBlock:^(NSString *photoPath) {
        NSLog(@"Photo upload error : %@", photoPath);

    } andSuccessBlock:^(NSString *photoPath) {

        NSLog(@"Photo upload ok : %@", photoPath);
        //then we delete the file
        [safeSelf mainUploadNextPhoto:photoSetID];
    }];
}
@end

Every network operations are done using AFNetworking this way :

-(void)myDummyDownload:(void (^)(NSData * data))successBlock
{
    AFHTTPClient* _httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:@"http://www.google.com/"]];
    [_httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];

    NSMutableURLRequest *request = [_httpClient requestWithMethod:@"GET" path:@"/" nil];
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];

    AFHTTPRequestOperation *operation = [_httpClient HTTPRequestOperationWithRequest:(NSURLRequest *)request
        success:^(AFHTTPRequestOperation *operation, id data) {
            if(dataBlock)
                dataBlock(data);
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"Cannot download : %@", error);
    }];

    [operation setShouldExecuteAsBackgroundTaskWithExpirationHandler:^{
        NSLog(@"Request time out");
    }];

    [_httpClient enqueueHTTPRequestOperation:operation];
}

My problem is : my connections are made asynchronously, so every task are launched together without waiting fo the previous to finish even with [self._queue setMaxConcurrentOperationCount:1] in SyncAgent.

Do I need to perform every connection synchronously ? I don't think this is a good idea, because a connection never should be done this way and also because I might use these methods elsewhere and need them to be performed in background, but I cannot find a better way. Any idea ?

Oh and if there is any error/typo in my code, I can assure you it appeared when I tried to summarize it before pasting it, it is working without any problem as of now.

Thanks !

PS: Sorry for the long pastes I couldn't figure out a better way to explain the problem.

EDIT: I found that using a semaphore is easier to set up and to understand : How do I wait for an asynchronously dispatched block to finish?

Community
  • 1
  • 1
dvkch
  • 1,079
  • 1
  • 12
  • 20

2 Answers2

2

If you have any control over the server at all, you should really consider creating an API that allows you to upload photos in an arbitrary order, so as to support multiple simultaneous uploads (which can be quite a bit faster for large batches).

But if you must do things synchronized, the easiest way is probably to enqueue new requests in the completion block of the requests. i.e.

// If [operations length] == 1, just enqueue it and skip all of this
NSEnumerator *enumerator = [operations reverseObjectEnumerator];
AFHTTPRequestOperation *currentOperation = nil;
AFHTTPRequestOperation *nextOperation = [enumerator nextObject]; 
while (nextOperation != nil && (currentOperation = [enumerator nextObject])) {
  currentOperation.completionBlock = ^{
    [client enqueueHTTPRequestOperation:nextOperation];
  }
  nextOperation = currentOperation;
}
[client enqueueHTTPRequestOperation:currentOperation];
mattt
  • 19,544
  • 7
  • 73
  • 84
  • unfortunately this doesn't work. The NSOperation launching the batch upload will still return before it has been uploaded. I would need a method for upload that returns only when the upload is finished/failed – dvkch Jul 05 '12 at 12:28
  • Then I misunderstood your requirements. Use `enqueueBatchOfHTTPRequestOperationsWithRequests:progressBlock:completionBlock:` – mattt Jul 05 '12 at 15:48
  • indeed this could be a way, but it will still not work unless I create a method creating the ID on the server, and then uploading every file. As of now I was using my SyncTask to do them in the right order only if needed and I prefer not to do it in my Web class to keep it very flexible. and using your way my SyncAgent will still "think" every SyncTask (NSOperation) have been finished. – dvkch Jul 06 '12 at 08:17
  • still cannot find a way to do so. I have tried -waitUntilFinished but it seems to wait for the connection timeout even if it was successful. weird. Any idea ? – dvkch Jul 09 '12 at 14:23
  • Stay away from `-waitUntilFinished` in general. Perhaps one thing to keep in mind is that operations are marked as `finished` when network requests finish loading--anything in the completion block will execute afterwards regardless. – mattt Jul 09 '12 at 14:27
  • Thanks, I will ! But I still cannot solve my problem. I still need to have a task list, and execute them one by one :/ , each one representing an album creation and mutliple uploads. – dvkch Jul 09 '12 at 14:35
1

The following code works for me, but I am not sure of the drawbacks. Take it with a pinch of salt.

 - (void) main {

  NSCondition* condition = [[NSCondition alloc] init];  
  __block bool hasData = false;
  [condition lock];

  [[WebAPI sharedClient] postPath:@"url" 
                        parameters:queryParams 
                           success:^(AFHTTPRequestOperation *operation, id JSON) {
                             //success code
                             [condition lock];
                             hasData = true;
                             [condition signal];
                             [condition unlock];

                           } 
                           failure:^(AFHTTPRequestOperation *operation, NSError *error) {

                             //failure code

                             [condition lock];
                             hasData = true;
                             [condition signal];
                             [condition unlock];
                           }];

  while (!hasData) {
    [condition wait];
  }
  [condition unlock];

}
Rakesh
  • 239
  • 3
  • 4
  • I must confess I think about it. This seams a little bit hacky but I think I'll do it anyway. I was thinking about using `dispatch_sync`to do it properly, have a single queue for all my uploads but this solution seams easier. – dvkch Jul 12 '12 at 10:49
  • 1
    You should replace NSCondition with GCD dispatch_groups. Mainly, look at `dispatch_group_enter`, `dispatch_group_wait`, and `dispatch_group_leave`. Mike Ash has [a good tutorial](http://www.mikeash.com/pyblog/friday-qa-2009-09-04-intro-to-grand-central-dispatch-part-ii-multi-core-performance.html). – Mark Sands Feb 05 '13 at 21:12