iOS interview questions 2018 (3)
Chapter 3: Messaging
This post explores messaging within Objective-C. Messaging is the terminology for invoking methods on an object. The format for a message expression is as follows (the brackets are required):
[object method]
or in
Objective-C parlance
[receiver message]
Here’s a simple example:
// Create an instance of SomeClass object
SomeClass *ptr = [[SomeClass alloc] init];
// Send the message ‘printInstanceVars’ to the ‘ptr’
receiver
[ptr printInstanceVars];
Every Objective-C object has a class, and every Objective-C
class has a list of methods. Each method has a selector, a
function pointer to the implementation, and some metadata. The
job of
objc_msgSend
is
to take the object and selector that's passed in, look up
the corresponding method's function pointer, and then jump
to that function pointer.
Looking up a method can be extremely complicated. If a method
isn’t found on a class, then it needs to continue searching in
the superclasses. If no method is found at all, then it needs to
call into the runtime’s message forwarding code. If this is the
very first message being sent to a particular class, then it has
to call that class’s
+initialize
method.
How lookup process works
Each class has a cache which stores methods as pairs of
selectors and function pointers, known in Objective-C as
IMP
s.
They're organized as a hash table so lookups are fast. When
looking up a method, the runtime first consults the cache. If
the method isn't in the cache, it follows the slow,
complicated procedure, and then places the result into the cache
so that the next time can be fast.
What objc_msgSend method does?
- Get the class of the object passed in.
- Get the method cache of that class.
- Use the selector passed in to look up the method in the cache.
- If it’s not in the cache, call into the C code.
-
Jump to the
IMP
for the method.
When a new object is created, memory for it is allocated, and
its instance variables are initialized. First among the object’s
variables is a pointer to its class structure. This pointer,
called isa
,
gives the object access to its class and, through the class, to
all the classes it inherits from.
When a message is sent to an object, the messaging function
follows the object’s
isa
pointer to
the class structure where it looks up the method selector in the
dispatch table. If it can’t find the selector there,
objc_msgSend
follows the pointer to the superclass and tries to find the
selector in its dispatch table. Successive failures cause
objc_msgSend
to
climb the class hierarchy until it reaches the
NSObject
class.
Once it locates the selector, the function calls the method
entered in the table and passes it the receiving object’s data
structure.
This is the way that method implementations are chosen at runtime — or, in the jargon of object-oriented programming, that methods are dynamically bound to messages.
Message Forwarding
What is message forwarding?
Simply speaking, it allows unknown messages to be trapped and reacted to. In other words, any time an unknown message is sent, it gets delivered to your code in a nice package, at which point you can do whatever you like with it.
What Happens?
What happens when you do
[customeDate tommorowDate]
and
customeDate
doesn't implement a
tommorowDate
method? When it
does implement such a
method, it's pretty straightforward: it looks up the
appropriate method, then jumps to it. When no such method can be
found, a complicated sequence of events ensues:
-
Lazy method resolution.
This is done by sending
resolveInstanceMethod:
(resolveClassMethod:
for class methods) to the class in question. If that method returns YES, the message send is restarted under the assumption that the appropriate method has now been added. -
Fast forwarding path.
This is done by sending
forwardingTargetForSelector:
to the target, if it implements it. If it implements this method and it returns something other thannil
orself
, the whole message sending process is restarted with that return value as the new target. -
Normal forwarding path.
First the runtime will send
methodSignatureForSelector:
to see what kind of argument and return types are present. If a method signature is returned, the runtime creates anNSInvocation
describing the message being sent and then sendsforwardInvocation:
to the object. If no method signature is found, the runtime sendsdoesNotRecognizeSelector:
.
Lazy Resolution
Doing this allows for really fast “forwarding”, because after
the method is resolved, it gets invoked as part of the normal
message sending process. This kind of thing is great for stuff
like @dynamic properties. Plug it in to the class using
+resolveInstanceMethod:
and off you go.
Fast Forwarding
This technique is great for faking multiple inheritence. You can write a little override like this:
- (id)forwardingTargetForSelector:(SEL)sel { return _otherObject; }
This will cause any unknown message to be sent to
_otherObject
,
which will make your object appear from the outside as though it
combined your object with this other object in one.
Normal Forwarding
The first two are basically just optimizations that allow
forwarding to go faster. If you don’t take advantage of them,
the full forwarding mechanism goes into action. This creates an
NSInvocation
object which fully encapsulates the message being sent. It holds
the target, the selector, and all of the arguments. It also
allows full control over the return value.
Before the runtime can build the
NSInvocation
it
needs an
NSMethodSignature
, so it requests one using
-methodSignatureForSelector:
. This is required due to Objective-C's C heritage. In
order to bundle the arguments up in the
NSInvocation
,
the runtime needs to know what kind of arguments there are, and
how many of them there are. This information isn't normally
provided in a C runtime environment, so it has to do an end run
around the C "bag of bytes" view of the world and get
that type information in another way.
Once the invocation is constructed, the runtime then invokes
your
forwardInvocation:
method. From there you can do whatever you want with the
invocation it hands you. The possibilities are endless.
Conclusion
It’s always interesting to dive into framework internals.
objc_msgSend
in
particular is a work of art. Message forwarding is a powerful
technique that greatly multiplies the expressiveness of
Objective-C. Cocoa uses it for things like
NSUndoManager
and distributed objects, and it can let you do a lot of nifty
things in your own code.
Hope this article is useful for people looking to gain knowledge as well as clear interview, Please ❤️ to recommend this post to others 😊. Let me know your feedback. :)