Dynamic Message Resolution in Objective C
Objective-C is a minimalistic extension of the C programming language. Minimalistic in the sense that the syntactic extensions can be summarized on just a few pages (all the keywords with an
@ in front of them). However, Objective-C also features a very powerful and versatile runtime environment that offers capabilities far beyond other systems.
Many object-oriented programming languages rely on object identities to be known at compile time, others can handle if object identities are resolved at runtime using introspection APIs. But the Objective-C runtime goes even further: even methods can be dynamically added and handled on the fly!
I started to play around and share the results below — a class can handle messages that were never declared in its interface. That works even if there are no methods corresponding to these messages at the time the classes were compiled! Admittedly, these features are not used frequently. Still, they are ubiquitous in the Core Data framework where all properties are declared as
Example Project for macOS
Asking the Boss to Get Work Done
Let’s start with the following code fragment:
This is really a non-exciting piece of code and the only thing looking odd about it is the use of
performSelector instead of the natural way of
[theBoss completeWork]. (The semantics of the object model seems a little odd, too – or who would design a class called “boss” and expect it to complete work?) It starts to get more exciting, however, when we have a look at the interface of the class
This class has no means of actually completing the work – there is no method declaration for doing so! (This makes the semantic of the object model a little more reasonable, but screws up the syntax.) At this point the reason for using
performSelector above also becomes clear: This code would not compile as the lexical analyzer knows that there is no method
completeWork and the way to get around this limitation is by using the
This still does not help as
WSBoss cannot handle the message and thus the code would simply crash. But we can actually get around this problem elegantly:
The trick is to forward the message to an instance of
WSEmployee instead of handling it herself. This approach will forward all messages sent to an instance of
WSBoss to the
worker says she can handle them (which it does by answering
worker cannot handle them (which includes the case that
nil) it will be forwarded to
WSBoss’s superclass which would result in an exception eventually.
Note that we need to override both
methodSignatureForSelector: as well as
forwardInvocation: methods for this approach to work!
How Work Really Gets Done
At this point we just need to let the instance of
WSEmployee do the work and everything will be fine. Still, we can do the same trick again:
WSEmployee still cannot handle the original message and thus cannot get anything done! Of course, he could simply try to forward the message elsewhere (where it still would not get done), but as our model is simply a small business with a single worker only, further delegation is not possible. Instead, the worker needs to get anything done that comes her way. This is how she does it:
The worker needs to implement a class method named
resolveInstanceMethod which takes a selector – a datatype representing a message signature – and returns whether or not it can handle that message. The runtime function
class_addMethod shows how an instance method is added to the class that can handle the message and will be called subsequently. The declaration of this function is in
objc/runtime.h so we need to import this header file.
Note that the function is a mere C function that does not return any value — we still need to worry about whether the function actually does the work it was supposed to do. (In our model the employee simply claims to have done them, but if that is really what we wanted is another issue!)
A Working Example
How does it all play out? Suppose, we instantiate a boss and a worker, set the
worker property of the boss and then send several messages her way:
If we do not set the
worker property of
theBoss the code would raise an exception. Otherwise, any message we send
theBoss will be forwarded to
aWorker who will handle everything that comes her way.
Completely Dynamical Objects and Messages
We can do even more! Up to this point we have assumed that the object names and messages are at least known at compile-time even though their interfaces were not. We can drop even this requirement and construct our classes and selectors entirely from strings (these could be read in at runtime). This does not give Objective-C the full power of a true
eval-statement, but it is the closest any dialect based on C can get:
In this case the class name
WSBoss, the configuration (setting the
worker property) as well as the actual job to be done (the message
anotherJob) is taken from strings. Finally, the
anotherJob message is handled by the poor employee who now has to deal with two different bosses! (Still, she does a pretty good job responding to everything she gets!)
It is also possible to extend this case to messages with more than two parameters and with non-object scalar data types. This requires the use of
NSInvocation directly – which, of course, can also be done dynamically!
In this post I have been playing with an advanced features of the Objective-C runtime — the ability to resolve message dynamically at runtime, even if the corresponding methods were unknown at compile time (or even development time)! Such behavioris used only in very specific cases (like for Core Data), but we should be aware they exist in case we might ever need them. For further information see the complete developer documentation on Apple’s site.
The source code of this project is available on github.com.