The key consideration is that the queue should be a serial queue. As the docs say:
The queue should be a serial queue, in order to ensure the correct ordering of callbacks.
So, if you instantiate your own OperationQueue, make sure to set its maxConcurrentOperationCount to 1.
The docs go on to say:
If [the delegate queue is] nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.
As such, we generally we would just leave this as nil, and let URLSession take care of this for us.
One generally would not use the main queue, though. This is largely a matter of convention (as URLSession.shared, which we generally use if we don’t need delegate methods or custom behaviors, uses a serial background queue). This practice, of using a serial background queue, is advisable, as you a lower the risk that some slow parsing operation (or whatever) in a delegate method or completion handler would ever affect your main thread responsiveness. That having been said, whenever using a serial background queue, make sure to dispatch UI updates (and the like) back to the main queue.