dvkch
dvkch

Reputation: 1109

AFNetworking Synchronous Operation in NSOperationQueue on iPhone

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?

Upvotes: 1

Views: 2939

Answers (2)

Rakesh
Rakesh

Reputation: 239

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];

}

Upvotes: 1

mattt
mattt

Reputation: 19544

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];

Upvotes: 2

Related Questions