10

I seem to have some confusion between dispatch_queue_t and NSOperationQueue queues.

By default, AFNetworking's AFImageRequestOperation will execute the success callback block on the application's main thread. To change this, AFHTTPRequestOperation has the property successCallbackQueue which lets you choose on which queue to run the callback.

I'm trying to execute the success callback on the same background queue / background threads which already did the HTTP request. Instead of returning to the main thread, the NSOperationQueue which ran the HTTP request should run the callback as well, since there are some heavy calculations I need to do using some of the returned images.

My first try was to set successCallbackQueue to the NSOperationQueue instance on which the AFImageRequestOperation ran. However, the successCallbackQueue property is of type dispatch_queue_t, so I need a way to get the underlying dispatch_queue_t of my NSOperation instance, if there is such a thing.

Is that possible, or do I need to create a separate dispatch_queue_t?

The reason I ask: It's somewhat strange that AFNetworking inherits from NSOperation, but expects us to use dispatch_queue_t queues for the callbacks. Kind of mixing the two paradigmas dispatch_queue_t and NSOperationQueue.

Thanks for any hints!

Jonas Sourlier
  • 13,684
  • 16
  • 77
  • 148

6 Answers6

9

There is no such thing, there isn't a one-to-one correspondence of an NSOperationQueue and a dispatch_queue_t, the queueing concepts in the two APIs are very different (e.g. NSOperationQueue does not have strict FIFO queueing like GCD does).

The only dispatch queue used by NSOperationQueue to execute your code is the default priority global concurrent queue.

das
  • 3,651
  • 17
  • 19
  • Pretty sure this wasn't true in 2013, but certainly Yosemite NSOperationQueue has an `underlyingQueue` property that shows a 1:1 correspondence. – Mark Nov 26 '14 at 11:00
  • no, not quite, that property is only non-nil if you have `setUnderlyingQueue:` earlier... otherwise there is a new dispatch queue made on demand for every execution of an NSOperation. In any case, these underlying dispatch queues remain only used for execution, the queueing of NSOperationQueue is still completely separate, and only once NSOperationQueue determines an operation should be run will it be added to a dispatch queue for execution. – das Dec 03 '14 at 08:16
  • I don't see how this answers the question. The question was, how do you get AFNetworking to call you back on the same thread where you called it. Is the answer you can't? Or you have to create a separate dispatch queue? – Liron Yahdav Apr 08 '15 at 19:56
  • There is an underlying queue even on iOS 9: just call bleQueue.underlyingQueue – Dario Sep 28 '15 at 01:47
  • 1
    You're right @jakenberg, you will have to manually create a dispatch_queue and assign it to your NSOperationQueue through setUnderlyingQueue – Dario Feb 03 '16 at 18:03
5

NSOperationQueue is not your bottleneck with AFNetworking. Request operations are bound by network, not CPU or memory. All of the work is done asynchronously in dispatch queues, which are accessible as properties in AFHTTPRequestOperation.

It is not advisable to use the network thread to do any processing. This will not improve performance in any way.

Instead, if you're noticing performance issues, try limiting the maximum number of concurrent operations in the operation queue, as a way to indirectly control the amount of work being done by those background processing queues.

mattt
  • 19,544
  • 7
  • 73
  • 84
3

It is interesting that AFHTTPClient uses an NSOperationQueue to run the AFHTTPRequestOperations but GCD dispatch_queues to handle the results.

Regarding NSOperationQueue the Apple docs say:

Note: In iOS 4 and later, operation queues use Grand Central Dispatch to execute operations.

but there doesn't seem to be a public API to get the dispatch_queue for a given operation.

If it's not that important to you that the success callback has to be on exactly the same queue/thread that the original operation was executed why not set:

successCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
Mike Pollard
  • 10,195
  • 2
  • 37
  • 46
  • Thanks, I'm gonna do that until maybe someone has a better option. – Jonas Sourlier May 15 '13 at 11:34
  • Yeah, not really an answer as such was it? Sorry :) – Mike Pollard May 15 '13 at 11:35
  • No problem, good reference for anyone having similar questions :) – Jonas Sourlier May 15 '13 at 11:51
  • 1
    Actually if you look at the implementation of AFURLConnectionOperation it creates a specific thread for doing the network call on anyway so in the end neither the NSOperationQueue nor GCD are going to manage where that stuff happens ... does it really have to be that complicated I wonder? – Mike Pollard May 15 '13 at 12:03
  • I'm downloading hundreds of small files from the web and I'm having performance issues, so I'm trying to find the best solution.. but you're right, this question was more something like **is `AFHTTPClient` using two kinds of queues, or is there a way get the underlying `dispatch_queue_t` of a `NSOperationQueue`?** – Jonas Sourlier May 15 '13 at 12:50
3

XCode 6.4 for iOS 8.4, ARC enabled

1) "...so I need a way to get the underlying dispatch_queue_t of my NSOperation instance, if there is such a thing."

There is a property of NSOperationQueue that can help:

@property(assign) dispatch_queue_t underlyingQueue

It can be used as follows to assign to NSOperationQueue:

NSOperationQueue *concurrentQueueForServerCommunication = [[NSOperationQueue alloc] init];
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    concurrentQueueForServerCommunication.underlyingQueue = concurrentQueue;

Or assign from NSOperationQueue:

NSOperationQueue *concurrentQueueForServerCommunication = [[NSOperationQueue alloc] init];
dispatch_queue_t concurrentQueue = concurrentQueueForServerCommunication.underlyingQueue;

Not sure if the API you are using for network communication updates your UI after the network task's completion, but just in case it does not, then you must know to get back onto the main queue when the completion block is executed:

dispatch_sync(dispatch_get_main_queue(), ^{

//Update your UI here...

}

Hope this helps! Cheers.

serge-k
  • 3,394
  • 2
  • 24
  • 55
  • @MarkoNikolovski Thanks Marko, glad I could help. Hopefully enough people notice this for the OP to mark my answer as correct. – serge-k Aug 08 '17 at 15:54
1

First of all, it's a good behaviour to execute the success of the AFImageRequestOperation on the main thread because the main usage of this operation is to download an image in background and display it on the UI (which should be on the main thread), but in order to satisfy the needs of those user (your case too) who want to execute the callback on other threads, there is also a successCalbackQueue.

So you can create your own dispatch_queue_t with dispatch_queue_create method or the recommended way, you should use dispatch_get_global_queue to get the main queue.

On each case, make sure that in your success block, if you are making some changes to the UI, put them inside dispatch_async(dispatch_get_main_queue(), ^{ // main op here});

danypata
  • 9,895
  • 1
  • 31
  • 44
  • It's only a good behaviour only if you want to do something related to the user interface. If you want to do a very heavy work (like image filtering or face detection) then is a bad behaviour. – Ricardo Apr 25 '14 at 12:45
  • To echo @Ricardo it is actually very common to have to do some amount of processing on response data whereby it is inappropriate to dump this on the main thread. – amcc Jul 11 '14 at 16:18
0

Swift 3 code, based on @serge-k's answer:

// Initialize the operation queue.
let operationQueue = OperationQueue()
operationQueue.name = "com.example.myOperationQueue"
operationQueue.qualityOfService = .userInitiated

// Initialize a backing DispatchQueue so we can reuse it for network operations.
// Because no additional info is give, the dispatch queue will have the same QoS as the operation queue.
let operationQueueUnderlyingQueue = DispatchQueue(label: "com.example.underlyingQueue")
operationQueue.qualityOfService.underlyingQueue = operationQueueUnderlyingQueue

You can then use this in Alamofire (or AFNetworking) in the following manner:

Alamofire.request("https://example.com/get", parameters: nil).validate().responseJSON(queue: operationQueue.underlyingQueue) { response in
    response handler code
}

A warning here, from Apple's documentation on setting the OperationQueue's underlying queue:

The value of this property should only be set if there are no operations in the queue; setting the value of this property when operationCount is not equal to 0 raises an invalidArgumentException. The value of this property must not be the value returned by dispatch_get_main_queue(). The quality-of-service level set for the underlying dispatch queue overrides any value set for the operation queue's qualityOfService property.

nikolovski
  • 4,049
  • 1
  • 31
  • 37