An Observation on Objective-C
(This post was originally posted in October of 2009 and has been reposted here for posterity)
I was thinking recently on the idea of “sending a message” versus that of “calling a method”. Those familiar with the capabilities of the Objective-C runtime understand that there is a difference.
Method Calling
Most compiled languages refer to methods and functions internally as offsets. The offset indicates that if one were to move a certain number of spaces in memory from a designated starting point, then we would find the beginning of the instructions that correspond to the desired function.
This has the advantage of being very fast. The starting point is known at compile-time, and so to find the appropriate section of memory takes just a couple of instructions. However, it has the disadvantage of being inflexible. This is where most compiled languages differ from Objective-C.
Message Sending
The Objective-C runtime maintains a list of all the methods and functions it knows about. The list has two components per entry: the name of the method (known as the method’s “selector”) and the location of the method in memory.
When an object attempts to “call a method”, it is really behaving quite differently. When the code was compiled, the compiler translated the code [anObject doMethod:aParameter];
into objc_msgSend(anObject, @selector(doMethod:), aParameter);
(basically. The actual behavior is slightly more complex but is all explained in the documentation).
The objc_msgSend()
function does a dynamic lookup. It knows the name of the method it’s supposed to find (@selector(doMethod:)
), and so it goes and looks up in its big list where it can find what it’s supposed to do next.
This allows us to do some really interesting things. For example, we can modify this list whenever we want. We can swap out values of the list, so that instead of selector A executing the code for A, selector A might instead point to the code for B. The runtime allows us to do some very powerful stuff.
The disadvantage is that this is (as you might imagine) slightly slower than directly jumping to the appropriate section in code. However, the difference is minute. Each message send takes just a couple nanoseconds longer than a regular method call.
Why this is so cool
In addition to dynamically modifying the runtime lookup table, the runtime does some other really nice things for us. For example, before it sends a message, it first asks the object if it even recognizes the method to begin with. The object can dynamically choose whether it wants to accept a message! The object can also choose to forward the message on to a different object. The object can also choose to execute different code for a specific method.
In other words, when we send a message, we don’t have any guarantee that 1) the method we’re intending to call is actually the method that will be called and 2) the object we’re intending to message is the one that will end up responding.
Usually, things work out as we expect. However, the runtime allows us to be exceptionally dynamic. This led me to make the following connection:
Calling a method is analogous to the active voice. Sending a message is analogous to passive voice.
(Or for you SAT-minded people: Calling a method :: Active voice ⇆ Sending a message :: Passive voice)
A brief lesson in grammar
In English (and most other languages), there are two ways we can associate a verb (an action) with a noun (the doer). One is with the active voice. For example: “The object did something.” In this construction there is no ambiguity as to who is performing. The doer takes full responsibility and accountability for performing the action. Likewise, when we use statically compiled languages that use offsets to immediately jump to the proper place in code, there is never any ambiguity as to what is going to happen. We know exactly which object is going to be executing the code, and we know exactly what code is going to be executed.
The other way we can construct sentences is using the passive voice. In this case, the sentence gets flipped around, like so: “Something was done by the object.” The emphasis is no longer on “the object”, but rather on what the object is supposed to be doing: “something.” If we leave off the noun, and just say “Something was done”, we know that something happened, but we have no idea who by. This is similar to sending a message in Objective-C (and other similar languages). When we send a message, we’re never totally sure that the person we want to respond to the message is the one who will actually respond. Instead, the focus is more on the message itself. We want something specific to happen, but as long as it gets done, we don’t really care who does it.
While the comparison itself to different voices in grammar isn’t itself very important, it is important to understand what the runtime is doing and how it is doing it. Understanding this allows us to do some really cool things in Objective-C that are not easily possible in other languages without significant amounts of work.