Search code examples
objective-cconcurrencynsoperationnsoperationqueueafnetworking

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?


Solution

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