Vtables can be annoying to follow through, but try reverse-engineering an Objective-C binary! Everything is dispatched dynamically, so 99% of the call graph ends in objc_msgSend(). Good luck figuring out what the message is, and the class of the object receiving it.
Isn't that easy? The message is a string in one of the register parameters to it.
> Everything is dispatched dynamically
Well, not everything, there is NS_DIRECT. The reason for that being that dynamic dispatch is expensive - you have to keep a lot of metadata about it in the heap for sometimes rarely-used messages. (It's not about CPU usage.)