A Look Under the Hood of objc_msgSend()
When I began to learn Objective C, I often heard of people talking about "sending message", I was curious, come on, it's just calling a method.
Then I recalled that I had that same feelings when functions become to methods as I began to touch C++.
To support polymorphism, C++ object model has a vtable inside every object which stores instance methods addresses, by which, C++ has a little dynamic characteristic.
But those dynamic are settled after compiling to machine code, unlike ruby or python, which has a virtual machine to dynamically interpreter the behaviors of a program, you might know, the duck typing.
Like C++, Objective C is also compiled to machine code, it supports
polymiorphism, but also, to another level, it supports duck typing, you
can send messages to any objects even the nil
.
What does it do to make it possible?
A Simple Program
Let's do some tests by first creating a simple program.
Example.h
1 |
|
Example.m
1 |
|
A class Example
is created, then a message is sent to
its instance p
.
Original Shape
Producing assembly by clang -S Example.m
->
Example.s
.
The result is a little complicated. Generally speaking, it does three things.
First, setup necessary information, then make a call to
_objc_msgSend
and store the return value to a local
variable.
1 | Ltmp9: |
Then we could jump to objc runtime source code to see what happens down the rabbit hole.
The Runtime
objc_msgSend
is called during every message sending,
which might happen millions of times only by booting the Mac OS X
system, obviously, it needs to be fine tuned, no surprise, it is written
in assembly.
Although those are assembly, they're fairly readable by the good naming conventions and explainable comments.
runtime/Messengers.subproj/objc-msg-x86_64.s
1 | ENTRY _objc_msgSend |
First, executes NilTest
macro, to check whether the
message sending target is nil, if nil, then returns nil, that's why we
could send messages to a nil object.
Then, uses GetIsaFast
to get the isa
address.
Every objc object has a member named isa
, it's the
blueprint of an object, which has all objc runtime needs to inspect an
object and see what its class is and then seeing if it responds to some
messages.
Finally, by CacheLookup
, objc runtime searches for the
selector responsible for the message in a class method cache, and
invokes the selector to finish the work.
Since objc is a object oriented language, some objects may inheritant hundreds of methods, but only some are frequently called, it's not efficient to look up all the selectors every time a message is needed to be sent.
If objc runtime failed to find the selector for a message, it jumps to handle the cache miss.
Then MethodTableLookup
will take the responsibility to
look up for the selector from the target isa
.
1 | LCacheMiss: |
The MethodTableLookup
simply transfers the
responsibility.
1 | .macro MethodTableLookup |
Again, responsibility is transferred to lookUpMethod
,
pay attention to the parameters assignments.
1 | // runtime/objc-class.mm |
Finally, lookup is done, and cache is refilled.
1 | // runtime/objc-class.mm |
If you want to step deeper, objc runtime source can be downloaded at this place.
At last, where do I find the objc runtime in the Mac OS X system?
/usr/lib/libobjc.A.dylib
Why am I so sure?
Since I lost my mind, wanted to remove the runtime to see what will happen.
First, Terminal stopped working, then Apps became irresponsible, no new App could be opened, and even can't reboot the whole system ...
Now I deeply know how important the objc runtime is in Mac OS X, and thank it, boot to recovery has a terminal to use, I was lucky.