Question

I want to call a unspecified number of URL requests which must be fired one after other. As the server can´t handle multiple requests with identical user-ID at the same time (only the last request is processed) i have to send my requests in an interval with about 1 seconds of gap. I did that within a dispatch_after block and increasing delays. But this is neither really secure nor elegant.

I´ve been just reading all day about GCD and want to try to change my code to send URL requests in a chain. My server connection class is build upon a NSURLConnection with asynchronuous request. That means it wouldn´t work with dispatch_async as the method call returns immediately back and the next request in the dispatch queue is called (which is probably immediately). But i have to wait for the response of the server until i may send the next request. My server connection class sends back via a delegate, but with dispatch_async it is never sending any deletate callbacks. Anyhow it wouldn´t work this way.

Probably it is better to put all requests into a NSArray and then call a method which will send requests from the array to the connection class and the delegate callback will pop the item from the array and sending the next request till all requests are done. Unfortunately i absolutely have no idea how i could store the requests and parameters in an array. Currently my call looks like that:

    - (void)sendSettings
{
    //NSLog(@"begins: %s", __FUNCTION__);

    dataProtocol = [[BackgroundSoundConnection alloc] init];
    [dataProtocol setDelegate:self];

    //double delayInSeconds;
    //dispatch_time_t popTime;
    //delayInSeconds = 0.1f;

    if (self.switch1.on)
    {
        if (![self.pinnedSettings.nextCall.globalId isEqualToString:self.sound.globalId]) {
                [dataProtocol requestDataFromServer:[NSString stringWithFormat:@"setBackgroundSoundNextCall/%@", self.sound.globalId] httpMethod:@"PUT" sound:self.sound stickerType:@"nextCall" personMSISDN:nil];
        }
    } else {
        if ([self.pinnedSettings.nextCall.globalId isEqualToString:self.sound.globalId]) {
                [dataProtocol requestDataFromServer:[NSString stringWithFormat:@"disableBackgroundSoundNextcall"] httpMethod:@"PUT" sound:nil stickerType:nil personMSISDN:nil];
        }
    }

    if (self.switch2.on)
    {
        if (![self.pinnedSettings.incomingCalls.globalId isEqualToString:self.sound.globalId]) {
                [dataProtocol requestDataFromServer:[NSString stringWithFormat:@"setBackgroundSoundIncoming/%@", self.sound.globalId] httpMethod:@"PUT" sound:self.sound stickerType:@"incomingCalls" personMSISDN:nil];
        }
    } else {
        if ([self.pinnedSettings.incomingCalls.globalId isEqualToString:self.sound.globalId]) {
                [dataProtocol requestDataFromServer:[NSString stringWithFormat:@"disableBackgroundSoundIncoming"] httpMethod:@"PUT" sound:nil stickerType:nil personMSISDN:nil];
            }
    }

    if (self.switch3.on)
    {
        if (![self.pinnedSettings.outgoingCalls.globalId isEqualToString:self.sound.globalId]) {
                [dataProtocol requestDataFromServer:[NSString stringWithFormat:@"setBackgroundSoundOutgoing/%@", self.sound.globalId] httpMethod:@"PUT" sound:self.sound stickerType:@"outgoingCalls" personMSISDN:nil];
        }
    } else {
        if ([self.pinnedSettings.outgoingCalls.globalId isEqualToString:self.sound.globalId]) {
                [dataProtocol requestDataFromServer:[NSString stringWithFormat:@"disableBackgroundSoundOutgoing"] httpMethod:@"PUT" sound:nil stickerType:nil personMSISDN:nil];
        }
    }

    for (int i = 0; i < [personArray count]; i++)
    {
        if (![personArray[i] connectedToServer])
        {
                NSLog(@"sound: %@", [personArray[i] soundId]);
                NSLog(@"msisdn: %@", [personArray[i] personMSISDN]);
                [dataProtocol requestDataFromServer:[NSString stringWithFormat:@"setBackgroundSoundContext/%@/%@", [personArray[i] soundId], [personArray[i] personMSISDN]] httpMethod:@"PUT" sound:self.sound stickerType:@"contextCalls" personMSISDN:[personArray[i] personMSISDN]];
        }
    }
    [self animateViewAway:self.view];
}

A part of the request parameters is already in an array. I could use this array and push the other request parameters into it and then sending the first parameter. And after server responded send the next request triggered by the callback from the delegate. Probably this would work.

But i´m just wondering if there isn´t andy way to que the requests a dispatch queue. But how could i que the delegates as well? Or what do i have to do that the queue will wait until the server responds? I´d like to avoid rewriting my server connection class from asynchronous to synchronous URLConnection which would probably make the difference.

Can anybody point me to a solution with asynchronous URLConnection and dispatch_async?

I haven´t seen the possibilites of NSOperation and NSOperationQueue yet. In the podcast of Jeff Kelley i´ve heard that the advantage of GCD over NSOperation is the dependencies feature. http://iphreaksshow.com/042-iphreaks-show-concurrency-with-jeff-kelley/

Or did i mix up everything? What would you recommend?

Was it helpful?

Solution

A complete NSURLRequest represents a complete request by containing a path, query params or body, headers, etc. You can build several of these to represent your several server requests.

NSURLConnection provides an asynch send (sendAsynchronousRequest:queue:completionHandler:). A naive way to sequence a series of requests, is to nest the requests in completion blocks as follows...

[NSURLConnection sendAsynchronousRequest:request0 queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
    if (!error) {
        [NSURLConnection sendAsynchronousRequest:request1 queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        if (!error) {
            // and so on... yikes, we'll have code in column 1000 pretty soon

But it should be clear that this is a weak idea. You can get the same effect for sequencing an arbitrary number of requests with pretty compact code as follows:

- (void)doManyRequests:(NSArray *)requests withResults:(NSMutableArray *)results completion:(void (^)(void))completion {

    if (!requests.count) {
        return completion();
    }
    NSURLRequest *nextRequest = requests[0];
    NSArray *remainingRequests = [requests subarrayWithRange:NSMakeRange(1, requests.count-1)];

    [NSURLConnection sendAsynchronousRequest:nextRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        [results addObject:data];
        [self doManyRequests:remainingRequests withResults:results completion:completion];
    }];
}

Now, as you suggested, prepare several requests and place them in an array:

NSURLRequest *request0 = // however you build this for a given user id
NSURLRequest *request1 = // etc.
NSURLRequest *request2 = // etc.

NSArray *requests = @[request0, request1, request2];
NSMutableArray *results = [NSMutableArray array];

[self doManyRequests:requests withResults:results completion:^{
    NSLog(@"this will be an array of NSData objects %@", results);
}];
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top