NSOperation* classes are a higher level of api. They hide from you a lower-level GCD api so you can focus on the task.
Rule of thumb: First use the highest level api and then degrade based on what you need to accomplish.
The advantage of this approach is that your code remains the most agnostic for the particular implementation provided by the provider. In this example, using NSOperation , you will use Apple's implementation of the execution queues (using GCD). If Apple ever decides to change implementation details behind the scenes, they can do it without breaking application code.
One such example would be Apple, which depreciates the GCD and uses a completely different library (which is unlikely because Apple created the GCD, and everyone seems to love it).
Regarding the question, I recommend referring to the following resources:
Now, regarding your specific questions:
What is the difference between sending _ * () and NSOperationQueue, [...]
See above.
[...] and is there any reason (technical, performance, style or otherwise) that I should use one over the other?
If NSOperation does your job, use it.
Is NSOperationQueue just an Objective-C wrapper around dispatch_async, or is there something else for this?
Yes, it is basically. Plus features like operational dependencies, simple start / stop.
Change
It is said that using the highest level of api may seem intriguing. Of course, if you need a quick way to run code in a specific thread, you donβt want to write many code templates, which makes the use of lower-level C functions perfectly acceptable:
dispatch_async(dispatch_get_main_queue(), ^{ do_something(); });
But consider this:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ do_something(); }];
I would recommend the latter, because most of what you write is Objective-C, so why not accept its expressiveness?