There are differences, but they are somewhat subtle.
Operations enqueued to -[NSOperationQueue mainQueue]
get executed one operation per pass of the run loop. This means, among other things, that there will be a "draw" pass between operations.
With dispatch_async(dispatch_get_main_queue(),...)
and -[performSelectorOnMainThread:...]
all enqueued blocks/selectors are called one after the other without spinning the run loop (i.e. allowing views to draw or anything like that). The runloop will continue after executing all enqueued blocks.
So, with respect to drawing, dispatch_async(dispatch_get_main_queue(),...)
and -[performSelectorOnMainThread:...]
batch operations into one draw pass, whereas -[NSOperationQueue mainQueue]
will draw after each operation.
For a full, in-depth investigation of this, see my answer over here.