1

I have a method :

-(void) dataForRequest:(NSMutableURLRequest*)url withCallback:(void (^)(NSData* data))callbackBlock` 

which gets data from the URL in the background, and then calls the callbackBlock with the received data.

I'm calling this on a serial queue like :

dispatch_async(my_serial_queue(), ^(void) {
    [dataForRequest:SOME_REQUEST withCallback:^(NSData *data) {
      // do something with data here.
}];

});

The reason I'm doing this on a serial queue is because i want the requests to get called one at a time, IN ORDER in which they were made.

But the problem I'm facing is, the requests are made in order, but the callbackBlock that I'm passing to the method is NOT getting called in order.

For e.g., request_1, request_2 and request_3 are submitted to the serial queue in proper order, but callBackBlock_1, callBackBlock_2 and callBackBlock_3 are not executed in the same order.

I understand why this is happening, but I am not able to figure out any solution to this. Any help will be appreciated. Thanks!

Sagar D
  • 2,588
  • 1
  • 18
  • 31
  • 2
    I don't know how to do it using GCD. But you can use NSOperationQueue where you can easily specify dependency between NSOperations so that 2nd task begins only after completion of 1st task. – Alok Rao Oct 12 '15 at 09:46
  • @AlokRao Your solution is not quite the same: the original idea was to call the requests in order (e.g. using a FIFO), then run them _concurrently_. Finally, when all are complete process the received data in order. A potential optimisation would be to process the data as it arrives AND if the previous data has already been processed. Doing this with NSOperation is possible, but very cumbersome. Doing this with GCD is possible, too - but tricky ;) – CouchDeveloper Oct 12 '15 at 14:57

2 Answers2

1

Here is a solution implemented in Swift, solely based on GCD. This approach is purely asynchronous, except the last statement dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER) which exists just to keep the main thread alive until after all tasks and continuations have been completed.

It shouldn't be too difficult to port this sample to Objective-C. The crux is probably to get the imported references of the dispatch_groups right.

The code below creates ten asynchronous tasks with the names "1", "2", .., and "10". The duration of each task is random between 0 and 8 seconds. The continuation of each task will print its name.

So, the output should be such, that the names will be printed in the same order as they have been scheduled - regardless of the duration. It can be the case, that names have been already printed while there are still tasks executing.

import Foundation


let queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL)


func task(duration: Double, completion: ()->()) {
    let t = dispatch_time(DISPATCH_TIME_NOW, (Int64)(duration * Double(NSEC_PER_SEC)))
    dispatch_after(t, dispatch_get_global_queue(0, 0)) {
        completion()
    }
}


func test(params: [String], continuation: ()->()) {
    let g0 = dispatch_group_create()
    dispatch_group_enter(g0)
    var g_prev = g0

    for s in params {
        let g = dispatch_group_create()
        dispatch_group_enter(g)
        let t = Double(arc4random() % 8)
        print("schedule task \"\(s)\" with duration \(t)")
        let gp = g_prev
        task(t) {
            print("finished task \"\(s)\"")
            dispatch_group_notify(gp, queue) { [g]
                print(s)
                dispatch_group_leave(g)
            }
        }
        g_prev = g

    }
    dispatch_group_leave(g0)

    dispatch_group_notify(g_prev, dispatch_get_global_queue(0, 0)) {
        continuation()
    }
}


let sem = dispatch_semaphore_create(0)
test(["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]) {
    print("finished")
    dispatch_semaphore_signal(sem)
}

dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER)
CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • You say that "It can be the case, that names have been already printed while there are still tasks executing." But in my case, i need the task to be completed before it calls the callbackBlock.. Because you see, the `callBackBlock` uses data fetched from the URL.. – Sagar D Oct 13 '15 at 05:08
  • @SagarD The approach above can be used to do the following: given an array of URLs. For each URL create an asynchronous task that fetches data from a server. Schedule the tasks in order, then process the returned data _in order_ (that is, process data from 1. URL, then data from 2. URL, etc.) When all data has been processed, the `test` function calls its completion handler. Is this what you want? Note: it's an implementation detail _how many_ requests will actually execute concurrently. If you use a `NSURLSession` you can configure the number of maximum concurrent requests. – CouchDeveloper Oct 13 '15 at 08:04
-2

There are a lot of possible implementations, but the basic principle here is that you want one request to complete before issuing the next.

As mentioned in the comments, you can create an NSOperation to encapsulate each request. Only signal the operation as complete after you've called your completion block. This approach is probably the best and least bug-prone.

Avi
  • 7,469
  • 2
  • 21
  • 22